Skip to content

Commit

Permalink
Add partial-match traversal of large bytes (#375)
Browse files Browse the repository at this point in the history
Implement an optional `LargeBytesNode` for an `AsLargeBytes() (io.ReadSeeker, error)` method. This allows, per the [selector spec](ipld/ipld#184) the ability to efficiently traverse sub-ranges of ADLs.
  • Loading branch information
willscott committed Mar 7, 2022
1 parent 9e783aa commit 78b188a
Show file tree
Hide file tree
Showing 19 changed files with 349 additions and 8 deletions.
13 changes: 10 additions & 3 deletions node/basicnode/bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (nb *plainBytes__Builder) Reset() {
// -- NodeAssembler -->

type plainBytes__Assembler struct {
w *plainBytes
w datamodel.Node
}

func (plainBytes__Assembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
Expand All @@ -131,17 +131,24 @@ func (plainBytes__Assembler) AssignString(string) error {
return mixins.BytesAssembler{TypeName: "bytes"}.AssignString("")
}
func (na *plainBytes__Assembler) AssignBytes(v []byte) error {
*na.w = plainBytes(v)
na.w = datamodel.Node(plainBytes(v))
return nil
}
func (plainBytes__Assembler) AssignLink(datamodel.Link) error {
return mixins.BytesAssembler{TypeName: "bytes"}.AssignLink(nil)
}
func (na *plainBytes__Assembler) AssignNode(v datamodel.Node) error {
if lb, ok := v.(datamodel.LargeBytesNode); ok {
lbn, err := lb.AsLargeBytes()
if err == nil {
na.w = streamBytes{lbn}
return nil
}
}
if v2, err := v.AsBytes(); err != nil {
return err
} else {
*na.w = plainBytes(v2)
na.w = plainBytes(v2)
return nil
}
}
Expand Down
81 changes: 81 additions & 0 deletions node/basicnode/bytes_stream.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package basicnode

import (
"io"

"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/node/mixins"
)

var (
_ datamodel.Node = streamBytes{nil}
_ datamodel.NodePrototype = Prototype__Bytes{}
_ datamodel.NodeBuilder = &plainBytes__Builder{}
_ datamodel.NodeAssembler = &plainBytes__Assembler{}
)

func NewBytesFromReader(rs io.ReadSeeker) datamodel.Node {
return streamBytes{rs}
}

// streamBytes is a boxed reader that complies with datamodel.Node.
type streamBytes struct {
io.ReadSeeker
}

// -- Node interface methods -->

func (streamBytes) Kind() datamodel.Kind {
return datamodel.Kind_Bytes
}
func (streamBytes) LookupByString(string) (datamodel.Node, error) {
return mixins.Bytes{TypeName: "bytes"}.LookupByString("")
}
func (streamBytes) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
return mixins.Bytes{TypeName: "bytes"}.LookupByNode(nil)
}
func (streamBytes) LookupByIndex(idx int64) (datamodel.Node, error) {
return mixins.Bytes{TypeName: "bytes"}.LookupByIndex(0)
}
func (streamBytes) LookupBySegment(seg datamodel.PathSegment) (datamodel.Node, error) {
return mixins.Bytes{TypeName: "bytes"}.LookupBySegment(seg)
}
func (streamBytes) MapIterator() datamodel.MapIterator {
return nil
}
func (streamBytes) ListIterator() datamodel.ListIterator {
return nil
}
func (streamBytes) Length() int64 {
return -1
}
func (streamBytes) IsAbsent() bool {
return false
}
func (streamBytes) IsNull() bool {
return false
}
func (streamBytes) AsBool() (bool, error) {
return mixins.Bytes{TypeName: "bytes"}.AsBool()
}
func (streamBytes) AsInt() (int64, error) {
return mixins.Bytes{TypeName: "bytes"}.AsInt()
}
func (streamBytes) AsFloat() (float64, error) {
return mixins.Bytes{TypeName: "bytes"}.AsFloat()
}
func (streamBytes) AsString() (string, error) {
return mixins.Bytes{TypeName: "bytes"}.AsString()
}
func (n streamBytes) AsBytes() ([]byte, error) {
return io.ReadAll(n)
}
func (streamBytes) AsLink() (datamodel.Link, error) {
return mixins.Bytes{TypeName: "bytes"}.AsLink()
}
func (streamBytes) Prototype() datamodel.NodePrototype {
return Prototype__Bytes{}
}
func (n streamBytes) AsLargeBytes() (io.ReadSeeker, error) {
return n.ReadSeeker, nil
}
12 changes: 12 additions & 0 deletions node/basicnode/bytes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package basicnode_test

import (
"testing"

"github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/ipld/go-ipld-prime/node/tests"
)

func TestBytes(t *testing.T) {
tests.SpecTestBytes(t, basicnode.Prototype__Bytes{})
}
35 changes: 35 additions & 0 deletions node/tests/byteSpecs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package tests

import (
"io"
"testing"

qt "github.com/frankban/quicktest"

"github.com/ipld/go-ipld-prime/datamodel"
)

func SpecTestBytes(t *testing.T, np datamodel.NodePrototype) {
t.Run("byte node", func(t *testing.T) {
nb := np.NewBuilder()
err := nb.AssignBytes([]byte("asdf"))
qt.Check(t, err, qt.IsNil)
n := nb.Build()

qt.Check(t, n.Kind(), qt.Equals, datamodel.Kind_Bytes)
qt.Check(t, n.IsNull(), qt.IsFalse)
x, err := n.AsBytes()
qt.Check(t, err, qt.IsNil)
qt.Check(t, x, qt.DeepEquals, []byte("asdf"))

lbn, ok := n.(datamodel.LargeBytesNode)
if ok {
str, err := lbn.AsLargeBytes()
qt.Check(t, err, qt.IsNil)
bytes, err := io.ReadAll(str)
qt.Check(t, err, qt.IsNil)
qt.Check(t, bytes, qt.DeepEquals, []byte("asdf"))
}

})
}
14 changes: 14 additions & 0 deletions traversal/selector/builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type SelectorSpecBuilder interface {
ExploreFields(ExploreFieldsSpecBuildingClosure) SelectorSpec
ExploreInterpretAs(as string, next SelectorSpec) SelectorSpec
Matcher() SelectorSpec
MatcherSubset(from, to int64) SelectorSpec
}

// ExploreFieldsSpecBuildingClosure is a function that provided to SelectorSpecBuilder's
Expand Down Expand Up @@ -170,6 +171,19 @@ func (ssb *selectorSpecBuilder) Matcher() SelectorSpec {
}
}

func (ssb *selectorSpecBuilder) MatcherSubset(from, to int64) SelectorSpec {
return selectorSpec{
fluent.MustBuildMap(ssb.np, 1, func(na fluent.MapAssembler) {
na.AssembleEntry(selector.SelectorKey_Matcher).CreateMap(1, func(na fluent.MapAssembler) {
na.AssembleEntry(selector.SelectorKey_Subset).CreateMap(2, func(na fluent.MapAssembler) {
na.AssembleEntry(selector.SelectorKey_From).AssignInt(from)
na.AssembleEntry(selector.SelectorKey_To).AssignInt(to)
})
})
}),
}
}

type exploreFieldsSpecBuilder struct {
na fluent.MapAssembler
}
Expand Down
5 changes: 5 additions & 0 deletions traversal/selector/exploreAll.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ func (s ExploreAll) Decide(n datamodel.Node) bool {
return false
}

// Match always returns false because this is not a matcher
func (s ExploreAll) Match(node datamodel.Node) (datamodel.Node, error) {
return nil, nil
}

// ParseExploreAll assembles a Selector from a ExploreAll selector node
func (pc ParseContext) ParseExploreAll(n datamodel.Node) (Selector, error) {
if n.Kind() != datamodel.Kind_Map {
Expand Down
5 changes: 5 additions & 0 deletions traversal/selector/exploreFields.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ func (s ExploreFields) Decide(n datamodel.Node) bool {
return false
}

// Match always returns false because this is not a matcher
func (s ExploreFields) Match(node datamodel.Node) (datamodel.Node, error) {
return nil, nil
}

// ParseExploreFields assembles a Selector
// from a ExploreFields selector node
func (pc ParseContext) ParseExploreFields(n datamodel.Node) (Selector, error) {
Expand Down
5 changes: 5 additions & 0 deletions traversal/selector/exploreIndex.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ func (s ExploreIndex) Decide(n datamodel.Node) bool {
return false
}

// Match always returns false because this is not a matcher
func (s ExploreIndex) Match(node datamodel.Node) (datamodel.Node, error) {
return nil, nil
}

// ParseExploreIndex assembles a Selector
// from a ExploreIndex selector node
func (pc ParseContext) ParseExploreIndex(n datamodel.Node) (Selector, error) {
Expand Down
5 changes: 5 additions & 0 deletions traversal/selector/exploreInterpretAs.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ func (s ExploreInterpretAs) Decide(n datamodel.Node) bool {
return false
}

// Match always returns false because this is not a matcher
func (s ExploreInterpretAs) Match(node datamodel.Node) (datamodel.Node, error) {
return nil, nil
}

// NamedReifier indicates how this selector expects to Reify the current datamodel.Node.
func (s ExploreInterpretAs) NamedReifier() string {
return s.adl
Expand Down
5 changes: 5 additions & 0 deletions traversal/selector/exploreRange.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ func (s ExploreRange) Decide(n datamodel.Node) bool {
return false
}

// Match always returns false because this is not a matcher
func (s ExploreRange) Match(node datamodel.Node) (datamodel.Node, error) {
return nil, nil
}

// ParseExploreRange assembles a Selector
// from a ExploreRange selector node
func (pc ParseContext) ParseExploreRange(n datamodel.Node) (Selector, error) {
Expand Down
5 changes: 5 additions & 0 deletions traversal/selector/exploreRecursive.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,11 @@ func (s ExploreRecursive) Decide(n datamodel.Node) bool {
return s.current.Decide(n)
}

// Match always returns false because this is not a matcher
func (s ExploreRecursive) Match(node datamodel.Node) (datamodel.Node, error) {
return s.current.Match(node)
}

type exploreRecursiveContext struct {
edgesFound int
}
Expand Down
5 changes: 5 additions & 0 deletions traversal/selector/exploreRecursiveEdge.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ func (s ExploreRecursiveEdge) Decide(n datamodel.Node) bool {
return false
}

// Match always returns false because this is not a matcher
func (s ExploreRecursiveEdge) Match(node datamodel.Node) (datamodel.Node, error) {
return nil, nil
}

// ParseExploreRecursiveEdge assembles a Selector
// from a exploreRecursiveEdge selector node
func (pc ParseContext) ParseExploreRecursiveEdge(n datamodel.Node) (Selector, error) {
Expand Down
12 changes: 12 additions & 0 deletions traversal/selector/exploreUnion.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ func (s ExploreUnion) Decide(n datamodel.Node) bool {
return false
}

// Match returns true for a Union selector based on the matched union.
func (s ExploreUnion) Match(n datamodel.Node) (datamodel.Node, error) {
for _, m := range s.Members {
if mn, err := m.Match(n); mn != nil {
return mn, nil
} else if err != nil {
return nil, err
}
}
return nil, nil
}

// ParseExploreUnion assembles a Selector
// from an ExploreUnion selector node
func (pc ParseContext) ParseExploreUnion(n datamodel.Node) (Selector, error) {
Expand Down
3 changes: 3 additions & 0 deletions traversal/selector/fieldKeys.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,8 @@ const (
SelectorKey_StopAt = "!"
SelectorKey_Condition = "&"
SelectorKey_As = "as"
SelectorKey_Subset = "subset"
SelectorKey_From = "["
SelectorKey_To = "]"
// not filling conditional keys since it's not complete
)

0 comments on commit 78b188a

Please sign in to comment.