github.com/ipld/go-ipld-prime@v0.21.0/traversal/focus_test.go (about)

     1  package traversal_test
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  
     7  	qt "github.com/frankban/quicktest"
     8  	"github.com/google/go-cmp/cmp"
     9  	"github.com/ipfs/go-cid"
    10  
    11  	_ "github.com/ipld/go-ipld-prime/codec/dagjson"
    12  	"github.com/ipld/go-ipld-prime/datamodel"
    13  	"github.com/ipld/go-ipld-prime/fluent"
    14  	"github.com/ipld/go-ipld-prime/linking"
    15  	cidlink "github.com/ipld/go-ipld-prime/linking/cid"
    16  	"github.com/ipld/go-ipld-prime/must"
    17  	"github.com/ipld/go-ipld-prime/node/basicnode"
    18  	nodetests "github.com/ipld/go-ipld-prime/node/tests"
    19  	"github.com/ipld/go-ipld-prime/storage/memstore"
    20  	"github.com/ipld/go-ipld-prime/traversal"
    21  )
    22  
    23  // Do some fixture fabrication.
    24  // We assume all the builders and serialization must Just Work here.
    25  
    26  var deepEqualsAllowAllUnexported = qt.CmpEquals(cmp.Exporter(func(reflect.Type) bool { return true }))
    27  
    28  var store = memstore.Store{}
    29  var (
    30  	// baguqeeyexkjwnfy
    31  	leafAlpha, leafAlphaLnk = encode(basicnode.NewString("alpha"))
    32  	// baguqeeyeqvc7t3a
    33  	leafBeta, leafBetaLnk = encode(basicnode.NewString("beta"))
    34  	// baguqeeyezhlahvq
    35  	middleMapNode, middleMapNodeLnk = encode(fluent.MustBuildMap(basicnode.Prototype.Map, 3, func(na fluent.MapAssembler) {
    36  		na.AssembleEntry("foo").AssignBool(true)
    37  		na.AssembleEntry("bar").AssignBool(false)
    38  		na.AssembleEntry("nested").CreateMap(2, func(na fluent.MapAssembler) {
    39  			na.AssembleEntry("alink").AssignLink(leafAlphaLnk)
    40  			na.AssembleEntry("nonlink").AssignString("zoo")
    41  		})
    42  	}))
    43  	// baguqeeyehfkkfwa
    44  	middleListNode, middleListNodeLnk = encode(fluent.MustBuildList(basicnode.Prototype.List, 4, func(na fluent.ListAssembler) {
    45  		na.AssembleValue().AssignLink(leafAlphaLnk)
    46  		na.AssembleValue().AssignLink(leafAlphaLnk)
    47  		na.AssembleValue().AssignLink(leafBetaLnk)
    48  		na.AssembleValue().AssignLink(leafAlphaLnk)
    49  	}))
    50  	// note that using `rootNode` directly will have a different field ordering than
    51  	// the encoded form if you were to load `rootNodeLnk` due to dag-json field
    52  	// reordering on encode, beware the difference for traversal order between
    53  	// created, in-memory nodes and those that have passed through a codec with
    54  	// field ordering rules
    55  	// baguqeeyeie4ajfy
    56  	rootNode, rootNodeLnk = encode(fluent.MustBuildMap(basicnode.Prototype.Map, 4, func(na fluent.MapAssembler) {
    57  		na.AssembleEntry("plain").AssignString("olde string")
    58  		na.AssembleEntry("linkedString").AssignLink(leafAlphaLnk)
    59  		na.AssembleEntry("linkedMap").AssignLink(middleMapNodeLnk)
    60  		na.AssembleEntry("linkedList").AssignLink(middleListNodeLnk)
    61  	}))
    62  )
    63  
    64  // encode hardcodes some encoding choices for ease of use in fixture generation;
    65  // just gimme a link and stuff the bytes in a map.
    66  // (also return the node again for convenient assignment.)
    67  func encode(n datamodel.Node) (datamodel.Node, datamodel.Link) {
    68  	lp := cidlink.LinkPrototype{Prefix: cid.Prefix{
    69  		Version:  1,
    70  		Codec:    0x0129,
    71  		MhType:   0x13,
    72  		MhLength: 4,
    73  	}}
    74  	lsys := cidlink.DefaultLinkSystem()
    75  	lsys.SetWriteStorage(&store)
    76  
    77  	lnk, err := lsys.Store(linking.LinkContext{}, lp, n)
    78  	if err != nil {
    79  		panic(err)
    80  	}
    81  	return n, lnk
    82  }
    83  
    84  // covers Focus used on one already-loaded Node; no link-loading exercised.
    85  func TestFocusSingleTree(t *testing.T) {
    86  	t.Run("empty path on scalar node returns start node", func(t *testing.T) {
    87  		err := traversal.Focus(basicnode.NewString("x"), datamodel.Path{}, func(prog traversal.Progress, n datamodel.Node) error {
    88  			qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("x"))
    89  			qt.Check(t, prog.Path.String(), qt.Equals, datamodel.Path{}.String())
    90  			return nil
    91  		})
    92  		qt.Check(t, err, qt.IsNil)
    93  	})
    94  	t.Run("one step path on map node works", func(t *testing.T) {
    95  		err := traversal.Focus(middleMapNode, datamodel.ParsePath("foo"), func(prog traversal.Progress, n datamodel.Node) error {
    96  			qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewBool(true))
    97  			qt.Check(t, prog.Path, deepEqualsAllowAllUnexported, datamodel.ParsePath("foo"))
    98  			return nil
    99  		})
   100  		qt.Check(t, err, qt.IsNil)
   101  	})
   102  	t.Run("two step path on map node works", func(t *testing.T) {
   103  		err := traversal.Focus(middleMapNode, datamodel.ParsePath("nested/nonlink"), func(prog traversal.Progress, n datamodel.Node) error {
   104  			qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("zoo"))
   105  			qt.Check(t, prog.Path, deepEqualsAllowAllUnexported, datamodel.ParsePath("nested/nonlink"))
   106  			return nil
   107  		})
   108  		qt.Check(t, err, qt.IsNil)
   109  	})
   110  }
   111  
   112  // covers Get used on one already-loaded Node; no link-loading exercised.
   113  // same fixtures as the test for Focus; just has fewer assertions, since Get does no progress tracking.
   114  func TestGetSingleTree(t *testing.T) {
   115  	t.Run("empty path on scalar node returns start node", func(t *testing.T) {
   116  		n, err := traversal.Get(basicnode.NewString("x"), datamodel.Path{})
   117  		qt.Check(t, err, qt.IsNil)
   118  		qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("x"))
   119  	})
   120  	t.Run("one step path on map node works", func(t *testing.T) {
   121  		n, err := traversal.Get(middleMapNode, datamodel.ParsePath("foo"))
   122  		qt.Check(t, err, qt.IsNil)
   123  		qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewBool(true))
   124  	})
   125  	t.Run("two step path on map node works", func(t *testing.T) {
   126  		n, err := traversal.Get(middleMapNode, datamodel.ParsePath("nested/nonlink"))
   127  		qt.Check(t, err, qt.IsNil)
   128  		qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("zoo"))
   129  	})
   130  }
   131  
   132  func TestFocusWithLinkLoading(t *testing.T) {
   133  	t.Run("link traversal with no configured loader should fail", func(t *testing.T) {
   134  		t.Run("terminal link should fail", func(t *testing.T) {
   135  			err := traversal.Focus(middleMapNode, datamodel.ParsePath("nested/alink"), func(prog traversal.Progress, n datamodel.Node) error {
   136  				t.Errorf("should not be reached; no way to load this path")
   137  				return nil
   138  			})
   139  			qt.Check(t, err.Error(), qt.Equals, `error traversing node at "nested/alink": could not load link "`+leafAlphaLnk.String()+`": no LinkTargetNodePrototypeChooser configured`)
   140  		})
   141  		t.Run("mid-path link should fail", func(t *testing.T) {
   142  			err := traversal.Focus(rootNode, datamodel.ParsePath("linkedMap/nested/nonlink"), func(prog traversal.Progress, n datamodel.Node) error {
   143  				t.Errorf("should not be reached; no way to load this path")
   144  				return nil
   145  			})
   146  			qt.Check(t, err.Error(), qt.Equals, `error traversing node at "linkedMap": could not load link "`+middleMapNodeLnk.String()+`": no LinkTargetNodePrototypeChooser configured`)
   147  		})
   148  	})
   149  	t.Run("link traversal with loader should work", func(t *testing.T) {
   150  		lsys := cidlink.DefaultLinkSystem()
   151  		lsys.SetReadStorage(&store)
   152  		err := traversal.Progress{
   153  			Cfg: &traversal.Config{
   154  				LinkSystem:                     lsys,
   155  				LinkTargetNodePrototypeChooser: basicnode.Chooser,
   156  			},
   157  		}.Focus(rootNode, datamodel.ParsePath("linkedMap/nested/nonlink"), func(prog traversal.Progress, n datamodel.Node) error {
   158  			qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("zoo"))
   159  			qt.Check(t, prog.Path, deepEqualsAllowAllUnexported, datamodel.ParsePath("linkedMap/nested/nonlink"))
   160  			qt.Check(t, prog.LastBlock.Link, deepEqualsAllowAllUnexported, middleMapNodeLnk)
   161  			qt.Check(t, prog.LastBlock.Path, deepEqualsAllowAllUnexported, datamodel.ParsePath("linkedMap"))
   162  			return nil
   163  		})
   164  		qt.Check(t, err, qt.IsNil)
   165  	})
   166  }
   167  
   168  func TestGetWithLinkLoading(t *testing.T) {
   169  	t.Run("link traversal with no configured loader should fail", func(t *testing.T) {
   170  		t.Run("terminal link should fail", func(t *testing.T) {
   171  			_, err := traversal.Get(middleMapNode, datamodel.ParsePath("nested/alink"))
   172  			qt.Check(t, err.Error(), qt.Equals, `error traversing node at "nested/alink": could not load link "`+leafAlphaLnk.String()+`": no LinkTargetNodePrototypeChooser configured`)
   173  		})
   174  		t.Run("mid-path link should fail", func(t *testing.T) {
   175  			_, err := traversal.Get(rootNode, datamodel.ParsePath("linkedMap/nested/nonlink"))
   176  			qt.Check(t, err.Error(), qt.Equals, `error traversing node at "linkedMap": could not load link "`+middleMapNodeLnk.String()+`": no LinkTargetNodePrototypeChooser configured`)
   177  		})
   178  	})
   179  	t.Run("link traversal with loader should work", func(t *testing.T) {
   180  		lsys := cidlink.DefaultLinkSystem()
   181  		lsys.SetReadStorage(&store)
   182  		n, err := traversal.Progress{
   183  			Cfg: &traversal.Config{
   184  				LinkSystem:                     lsys,
   185  				LinkTargetNodePrototypeChooser: basicnode.Chooser,
   186  			},
   187  		}.Get(rootNode, datamodel.ParsePath("linkedMap/nested/nonlink"))
   188  		qt.Check(t, err, qt.IsNil)
   189  		qt.Check(t, n, nodetests.NodeContentEquals, basicnode.NewString("zoo"))
   190  	})
   191  }
   192  
   193  func TestFocusedTransform(t *testing.T) {
   194  	t.Run("UpdateMapEntry", func(t *testing.T) {
   195  		n, err := traversal.FocusedTransform(rootNode, datamodel.ParsePath("plain"), func(progress traversal.Progress, prev datamodel.Node) (datamodel.Node, error) {
   196  			qt.Check(t, progress.Path.String(), qt.Equals, "plain")
   197  			qt.Check(t, must.String(prev), qt.Equals, "olde string")
   198  			nb := prev.Prototype().NewBuilder()
   199  			nb.AssignString("new string!")
   200  			return nb.Build(), nil
   201  		}, false)
   202  		qt.Check(t, err, qt.IsNil)
   203  		qt.Check(t, n.Kind(), qt.Equals, datamodel.Kind_Map)
   204  		// updated value should be there
   205  		qt.Check(t, must.Node(n.LookupByString("plain")), nodetests.NodeContentEquals, basicnode.NewString("new string!"))
   206  		// everything else should be there
   207  		qt.Check(t, must.Node(n.LookupByString("linkedString")), qt.Equals, must.Node(rootNode.LookupByString("linkedString")))
   208  		qt.Check(t, must.Node(n.LookupByString("linkedMap")), qt.Equals, must.Node(rootNode.LookupByString("linkedMap")))
   209  		qt.Check(t, must.Node(n.LookupByString("linkedList")), qt.Equals, must.Node(rootNode.LookupByString("linkedList")))
   210  		// everything should still be in the same order
   211  		qt.Check(t, keys(n), qt.DeepEquals, []string{"plain", "linkedString", "linkedMap", "linkedList"})
   212  	})
   213  	t.Run("UpdateDeeperMap", func(t *testing.T) {
   214  		n, err := traversal.FocusedTransform(middleMapNode, datamodel.ParsePath("nested/alink"), func(progress traversal.Progress, prev datamodel.Node) (datamodel.Node, error) {
   215  			qt.Check(t, progress.Path.String(), qt.Equals, "nested/alink")
   216  			qt.Check(t, prev, nodetests.NodeContentEquals, basicnode.NewLink(leafAlphaLnk))
   217  			return basicnode.NewString("new string!"), nil
   218  		}, false)
   219  		qt.Check(t, err, qt.IsNil)
   220  		qt.Check(t, n.Kind(), qt.Equals, datamodel.Kind_Map)
   221  		// updated value should be there
   222  		qt.Check(t, must.Node(must.Node(n.LookupByString("nested")).LookupByString("alink")), nodetests.NodeContentEquals, basicnode.NewString("new string!"))
   223  		// everything else in the parent map should should be there!
   224  		qt.Check(t, must.Node(n.LookupByString("foo")), qt.Equals, must.Node(middleMapNode.LookupByString("foo")))
   225  		qt.Check(t, must.Node(n.LookupByString("bar")), qt.Equals, must.Node(middleMapNode.LookupByString("bar")))
   226  		// everything should still be in the same order
   227  		qt.Check(t, keys(n), qt.DeepEquals, []string{"foo", "bar", "nested"})
   228  	})
   229  	t.Run("AppendIfNotExists", func(t *testing.T) {
   230  		n, err := traversal.FocusedTransform(rootNode, datamodel.ParsePath("newpart"), func(progress traversal.Progress, prev datamodel.Node) (datamodel.Node, error) {
   231  			qt.Check(t, progress.Path.String(), qt.Equals, "newpart")
   232  			qt.Check(t, prev, qt.IsNil) // REVIEW: should datamodel.Absent be used here?  I lean towards "no" but am unsure what's least surprising here.
   233  			// An interesting thing to note about inserting a value this way is that you have no `prev.Prototype().NewBuilder()` to use if you wanted to.
   234  			//  But if that's an issue, then what you do is a focus or walk (transforming or not) to the parent node, get its child prototypes, and go from there.
   235  			return basicnode.NewString("new string!"), nil
   236  		}, false)
   237  		qt.Check(t, err, qt.IsNil)
   238  		qt.Check(t, n.Kind(), qt.Equals, datamodel.Kind_Map)
   239  		// updated value should be there
   240  		qt.Check(t, must.Node(n.LookupByString("newpart")), nodetests.NodeContentEquals, basicnode.NewString("new string!"))
   241  		// everything should still be in the same order... with the new entry at the end.
   242  		qt.Check(t, keys(n), qt.DeepEquals, []string{"plain", "linkedString", "linkedMap", "linkedList", "newpart"})
   243  	})
   244  	t.Run("CreateParents", func(t *testing.T) {
   245  		n, err := traversal.FocusedTransform(rootNode, datamodel.ParsePath("newsection/newpart"), func(progress traversal.Progress, prev datamodel.Node) (datamodel.Node, error) {
   246  			qt.Check(t, progress.Path.String(), qt.Equals, "newsection/newpart")
   247  			qt.Check(t, prev, qt.IsNil) // REVIEW: should datamodel.Absent be used here?  I lean towards "no" but am unsure what's least surprising here.
   248  			return basicnode.NewString("new string!"), nil
   249  		}, true)
   250  		qt.Check(t, err, qt.IsNil)
   251  		qt.Check(t, n.Kind(), qt.Equals, datamodel.Kind_Map)
   252  		// a new map node in the middle should've been created
   253  		n2 := must.Node(n.LookupByString("newsection"))
   254  		qt.Check(t, n2.Kind(), qt.Equals, datamodel.Kind_Map)
   255  		// updated value should in there
   256  		qt.Check(t, must.Node(n2.LookupByString("newpart")), nodetests.NodeContentEquals, basicnode.NewString("new string!"))
   257  		// everything in the root map should still be in the same order... with the new entry at the end.
   258  		qt.Check(t, keys(n), qt.DeepEquals, []string{"plain", "linkedString", "linkedMap", "linkedList", "newsection"})
   259  		// and the created intermediate map of course has just one entry.
   260  		qt.Check(t, keys(n2), qt.DeepEquals, []string{"newpart"})
   261  	})
   262  	t.Run("CreateParentsRequiresPermission", func(t *testing.T) {
   263  		_, err := traversal.FocusedTransform(rootNode, datamodel.ParsePath("newsection/newpart"), func(progress traversal.Progress, prev datamodel.Node) (datamodel.Node, error) {
   264  			qt.Check(t, true, qt.IsFalse) // ought not be reached
   265  			return nil, nil
   266  		}, false)
   267  		qt.Check(t, err.Error(), qt.Equals, "transform: parent position at \"newsection\" did not exist (and createParents was false)")
   268  	})
   269  	t.Run("UpdateListEntry", func(t *testing.T) {
   270  		n, err := traversal.FocusedTransform(middleListNode, datamodel.ParsePath("2"), func(progress traversal.Progress, prev datamodel.Node) (datamodel.Node, error) {
   271  			qt.Check(t, progress.Path.String(), qt.Equals, "2")
   272  			qt.Check(t, prev, nodetests.NodeContentEquals, basicnode.NewLink(leafBetaLnk))
   273  			return basicnode.NewString("new string!"), nil
   274  		}, false)
   275  		qt.Check(t, err, qt.IsNil)
   276  		qt.Check(t, n.Kind(), qt.Equals, datamodel.Kind_List)
   277  		// updated value should be there
   278  		qt.Check(t, must.Node(n.LookupByIndex(2)), nodetests.NodeContentEquals, basicnode.NewString("new string!"))
   279  		// everything else should be there
   280  		qt.Check(t, n.Length(), qt.Equals, int64(4))
   281  		qt.Check(t, must.Node(n.LookupByIndex(0)), nodetests.NodeContentEquals, basicnode.NewLink(leafAlphaLnk))
   282  		qt.Check(t, must.Node(n.LookupByIndex(1)), nodetests.NodeContentEquals, basicnode.NewLink(leafAlphaLnk))
   283  		qt.Check(t, must.Node(n.LookupByIndex(3)), nodetests.NodeContentEquals, basicnode.NewLink(leafAlphaLnk))
   284  	})
   285  	t.Run("AppendToList", func(t *testing.T) {
   286  		n, err := traversal.FocusedTransform(middleListNode, datamodel.ParsePath("-"), func(progress traversal.Progress, prev datamodel.Node) (datamodel.Node, error) {
   287  			qt.Check(t, progress.Path.String(), qt.Equals, "4")
   288  			qt.Check(t, prev, qt.IsNil)
   289  			return basicnode.NewString("new string!"), nil
   290  		}, false)
   291  		qt.Check(t, err, qt.IsNil)
   292  		qt.Check(t, n.Kind(), qt.Equals, datamodel.Kind_List)
   293  		// updated value should be there
   294  		qt.Check(t, must.Node(n.LookupByIndex(4)), nodetests.NodeContentEquals, basicnode.NewString("new string!"))
   295  		// everything else should be there
   296  		qt.Check(t, n.Length(), qt.Equals, int64(5))
   297  	})
   298  	t.Run("ListBounds", func(t *testing.T) {
   299  		_, err := traversal.FocusedTransform(middleListNode, datamodel.ParsePath("4"), func(progress traversal.Progress, prev datamodel.Node) (datamodel.Node, error) {
   300  			qt.Check(t, true, qt.IsFalse) // ought not be reached
   301  			return nil, nil
   302  		}, false)
   303  		qt.Check(t, err, qt.ErrorMatches, "transform: cannot navigate path segment \"4\" at \"\" because it is beyond the list bounds")
   304  	})
   305  	t.Run("ReplaceRoot", func(t *testing.T) { // a fairly degenerate case and no reason to do this, but should work.
   306  		n, err := traversal.FocusedTransform(middleListNode, datamodel.ParsePath(""), func(progress traversal.Progress, prev datamodel.Node) (datamodel.Node, error) {
   307  			qt.Check(t, progress.Path.String(), qt.Equals, "")
   308  			qt.Check(t, prev, nodetests.NodeContentEquals, middleListNode)
   309  			nb := basicnode.Prototype.Any.NewBuilder()
   310  			la, _ := nb.BeginList(0)
   311  			la.Finish()
   312  			return nb.Build(), nil
   313  		}, false)
   314  		qt.Check(t, err, qt.IsNil)
   315  		qt.Check(t, n.Kind(), qt.Equals, datamodel.Kind_List)
   316  		qt.Check(t, n.Length(), qt.Equals, int64(0))
   317  	})
   318  }
   319  
   320  func TestFocusedTransformWithLinks(t *testing.T) {
   321  	var store2 = memstore.Store{}
   322  	lsys := cidlink.DefaultLinkSystem()
   323  	lsys.SetReadStorage(&store)
   324  	lsys.SetWriteStorage(&store2)
   325  	cfg := traversal.Config{
   326  		LinkSystem:                     lsys,
   327  		LinkTargetNodePrototypeChooser: basicnode.Chooser,
   328  	}
   329  	t.Run("UpdateMapBeyondLink", func(t *testing.T) {
   330  		n, err := traversal.Progress{
   331  			Cfg: &cfg,
   332  		}.FocusedTransform(rootNode, datamodel.ParsePath("linkedMap/nested/nonlink"), func(progress traversal.Progress, prev datamodel.Node) (datamodel.Node, error) {
   333  			qt.Check(t, progress.Path.String(), qt.Equals, "linkedMap/nested/nonlink")
   334  			qt.Check(t, must.String(prev), qt.Equals, "zoo")
   335  			qt.Check(t, progress.LastBlock.Path.String(), qt.Equals, "linkedMap")
   336  			qt.Check(t, progress.LastBlock.Link.String(), qt.Equals, "baguqeeyezhlahvq")
   337  			nb := prev.Prototype().NewBuilder()
   338  			nb.AssignString("new string!")
   339  			return nb.Build(), nil
   340  		}, false)
   341  		qt.Check(t, err, qt.IsNil)
   342  		qt.Check(t, n.Kind(), qt.Equals, datamodel.Kind_Map)
   343  		// there should be a new object in our new storage!
   344  		qt.Check(t, store2.Bag, qt.HasLen, 1)
   345  		// cleanup for next test
   346  		store2 = memstore.Store{}
   347  	})
   348  	t.Run("UpdateNotBeyondLink", func(t *testing.T) {
   349  		// This is replacing a link with a non-link.  Doing so shouldn't hit storage.
   350  		n, err := traversal.Progress{
   351  			Cfg: &cfg,
   352  		}.FocusedTransform(rootNode, datamodel.ParsePath("linkedMap"), func(progress traversal.Progress, prev datamodel.Node) (datamodel.Node, error) {
   353  			qt.Check(t, progress.Path.String(), qt.Equals, "linkedMap")
   354  			nb := prev.Prototype().NewBuilder()
   355  			nb.AssignString("new string!")
   356  			return nb.Build(), nil
   357  		}, false)
   358  		qt.Check(t, err, qt.IsNil)
   359  		qt.Check(t, n.Kind(), qt.Equals, datamodel.Kind_Map)
   360  		// there should be no new objects in our new storage!
   361  		qt.Check(t, store2.Bag, qt.HasLen, 0)
   362  		// cleanup for next test
   363  		store2 = memstore.Store{}
   364  	})
   365  
   366  	// link traverse to scalar // this is unspecifiable using the current path syntax!  you'll just end up replacing the link with the scalar!
   367  }
   368  
   369  func keys(n datamodel.Node) []string {
   370  	v := make([]string, 0, n.Length())
   371  	for itr := n.MapIterator(); !itr.Done(); {
   372  		k, _, _ := itr.Next()
   373  		v = append(v, must.String(k))
   374  	}
   375  	return v
   376  }