Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add partial-match traversal of large bytes #375

Merged
merged 14 commits into from
Mar 7, 2022
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
}
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
)
99 changes: 98 additions & 1 deletion traversal/selector/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package selector

import (
"fmt"
"io"

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

// Matcher marks a node to be included in the "result" set.
Expand All @@ -16,7 +18,61 @@ import (
// A selector tree with only "explore*"-type selectors and no Matcher selectors
// is valid; it will just generate a "covered" set of nodes and no "result" set.
// TODO: From spec: implement conditions and labels
type Matcher struct{}
type Matcher struct {
*Slice
}

// Slice limits a result node to a subset of the node.
// The returned node will be limited based on slicing the specified range of the
// node into a new node, or making use of the `AsLargeBytes` io.ReadSeeker to
// restrict response with a SectionReader.
type Slice struct {
From int64
To int64
}

func (s Slice) Slice(n datamodel.Node) (datamodel.Node, error) {
if n.Kind() == datamodel.Kind_String {
willscott marked this conversation as resolved.
Show resolved Hide resolved
str, err := n.AsString()
if err != nil {
return nil, err
}
to := s.To
if len(str) < int(to) {
to = int64(len(str))
}
from := s.From
if len(str) < int(from) {
from = int64(len(str))
}
return basicnode.NewString(str[from:to]), nil
} else if n.Kind() == datamodel.Kind_Bytes {
if lbn, ok := n.(datamodel.LargeBytesNode); ok {
rdr, err := lbn.AsLargeBytes()
if err != nil {
return nil, err
}

sr := io.NewSectionReader(readerat{rdr}, int64(s.From), int64(s.To))
willscott marked this conversation as resolved.
Show resolved Hide resolved
return basicnode.NewBytesFromReader(sr), nil
}
bytes, err := n.AsBytes()
if err != nil {
return nil, err
}
to := s.To
willscott marked this conversation as resolved.
Show resolved Hide resolved
if len(bytes) < int(to) {
to = int64(len(bytes))
}
from := s.From
if len(bytes) < int(from) {
from = int64(len(bytes))
}

return basicnode.NewBytes(bytes[from:to]), nil
}
return nil, fmt.Errorf("selector slice rejected: subset match must be over string or bytes")
}

// Interests are empty for a matcher (for now) because
// It is always just there to match, not explore further
Expand All @@ -35,12 +91,53 @@ func (s Matcher) Decide(n datamodel.Node) bool {
return true
}

// Match is always true for a match cause it's in the result set
func (s Matcher) Match(node datamodel.Node) (datamodel.Node, error) {
if s.Slice != nil {
return s.Slice.Slice(node)
}
return node, nil
}

// ParseMatcher assembles a Selector
// from a matcher selector node
// TODO: Parse labels and conditions
func (pc ParseContext) ParseMatcher(n datamodel.Node) (Selector, error) {
if n.Kind() != datamodel.Kind_Map {
return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map")
}

// check if a slice is specified
if subset, err := n.LookupByString("subset"); err == nil {
if subset.Kind() != datamodel.Kind_Map {
return nil, fmt.Errorf("selector spec parse rejected: subset body must be a map")
}
from, err := subset.LookupByString("[")
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map with a from '[' key")
}
fromN, err := from.AsInt()
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map with a 'from' key that is a number")
}
to, err := subset.LookupByString("]")
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map with a to ']' key")
}
toN, err := to.AsInt()
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map with a 'to' key that is a number")
}
if fromN > toN {
return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map with a 'from' key that is less than or equal to the 'to' key")
}
if fromN < 0 || toN < 0 {
return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map with keys not less than 0")
}
return Matcher{&Slice{
From: fromN,
To: toN,
}}, nil
}
return Matcher{}, nil
}
Loading