Skip to content

Commit

Permalink
feat: ipld amend
Browse files Browse the repository at this point in the history
  • Loading branch information
smrz2001 committed Jul 3, 2023
1 parent 65bfa53 commit 437510b
Show file tree
Hide file tree
Showing 6 changed files with 340 additions and 78 deletions.
12 changes: 12 additions & 0 deletions datamodel/amender.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package datamodel

// NodeAmender layers onto NodeBuilder the ability to transform all or part of a Node under construction. This bypasses
// the
//
// To achieve this, NodeAmender exposes a Transform function that can take in the Node (or a child Node in case of
// recursive nodes)
type NodeAmender interface {
NodeBuilder

Transform(path Path, transform func(Node) (NodeAmender, error)) (Node, error)
}
2 changes: 1 addition & 1 deletion datamodel/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ type NodePrototype interface {
// volumes of data, detecting and using this feature can result in significant
// performance savings.
type NodePrototypeSupportingAmend interface {
AmendingBuilder(base Node) NodeBuilder
AmendingBuilder(base Node) NodeAmender
// FUTURE: probably also needs a `AmendingWithout(base Node, filter func(k,v) bool) NodeBuilder`, or similar.
// ("deletion" based APIs are also possible but both more complicated in interfaces added, and prone to accidentally quadratic usage.)
// FUTURE: there should be some stdlib `Copy` (?) methods that automatically look for this feature, and fallback if absent.
Expand Down
95 changes: 64 additions & 31 deletions node/basicnode/any.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import (

var (
//_ datamodel.Node = &anyNode{}
_ datamodel.NodePrototype = Prototype__Any{}
_ datamodel.NodeBuilder = &anyBuilder{}
_ datamodel.NodePrototype = Prototype__Any{}
_ datamodel.NodePrototypeSupportingAmend = Prototype__Any{}
_ datamodel.NodeBuilder = &anyBuilder{}
//_ datamodel.NodeAssembler = &anyAssembler{}
)

Expand All @@ -34,8 +35,24 @@ func Chooser(_ datamodel.Link, _ linking.LinkContext) (datamodel.NodePrototype,

type Prototype__Any struct{}

func (Prototype__Any) NewBuilder() datamodel.NodeBuilder {
return &anyBuilder{}
func (p Prototype__Any) NewBuilder() datamodel.NodeBuilder {
return p.AmendingBuilder(nil)
}

// -- NodePrototypeSupportingAmend -->

func (p Prototype__Any) AmendingBuilder(base datamodel.Node) datamodel.NodeAmender {
ab := &anyBuilder{}
if base != nil {
ab.kind = base.Kind()
if npa, castOk := base.Prototype().(datamodel.NodePrototypeSupportingAmend); castOk {
ab.amender = npa.AmendingBuilder(base)
} else {
// This node could be either scalar or recursive
ab.baseNode = base
}
}
return ab
}

// -- NodeBuilder -->
Expand All @@ -57,17 +74,16 @@ type anyBuilder struct {
kind datamodel.Kind

// Only one of the following ends up being used...
// but we don't know in advance which one, so all are embeded here.
// but we don't know in advance which one, so both are embedded here.
// This uses excessive space, but amortizes allocations, and all will be
// freed as soon as the builder is done.
// Builders are only used for recursives;
// scalars are simple enough we just do them directly.
// 'scalarNode' may also hold another Node of unknown prototype (possibly not even from this package),
// An amender is only used for amendable nodes, while all non-amendable nodes (both recursives and scalars) are
// stored directly.
// 'baseNode' may also hold another Node of unknown prototype (possibly not even from this package),
// in which case this is indicated by 'kind==99'.

mapBuilder plainMap__Builder
listBuilder plainList__Builder
scalarNode datamodel.Node
amender datamodel.NodeAmender
baseNode datamodel.Node
}

func (nb *anyBuilder) Reset() {
Expand All @@ -79,16 +95,18 @@ func (nb *anyBuilder) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
panic("misuse")
}
nb.kind = datamodel.Kind_Map
nb.mapBuilder.w = &plainMap{}
return nb.mapBuilder.BeginMap(sizeHint)
mapBuilder := Prototype.Map.NewBuilder().(*plainMap__Builder)
nb.amender = mapBuilder
return mapBuilder.BeginMap(sizeHint)
}
func (nb *anyBuilder) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
if nb.kind != datamodel.Kind_Invalid {
panic("misuse")
}
nb.kind = datamodel.Kind_List
nb.listBuilder.w = &plainList{}
return nb.listBuilder.BeginList(sizeHint)
listBuilder := Prototype.List.NewBuilder().(*plainList__Builder)
nb.amender = listBuilder
return listBuilder.BeginList(sizeHint)
}
func (nb *anyBuilder) AssignNull() error {
if nb.kind != datamodel.Kind_Invalid {
Expand All @@ -102,90 +120,105 @@ func (nb *anyBuilder) AssignBool(v bool) error {
panic("misuse")
}
nb.kind = datamodel.Kind_Bool
nb.scalarNode = NewBool(v)
nb.baseNode = NewBool(v)
return nil
}
func (nb *anyBuilder) AssignInt(v int64) error {
if nb.kind != datamodel.Kind_Invalid {
panic("misuse")
}
nb.kind = datamodel.Kind_Int
nb.scalarNode = NewInt(v)
nb.baseNode = NewInt(v)
return nil
}
func (nb *anyBuilder) AssignFloat(v float64) error {
if nb.kind != datamodel.Kind_Invalid {
panic("misuse")
}
nb.kind = datamodel.Kind_Float
nb.scalarNode = NewFloat(v)
nb.baseNode = NewFloat(v)
return nil
}
func (nb *anyBuilder) AssignString(v string) error {
if nb.kind != datamodel.Kind_Invalid {
panic("misuse")
}
nb.kind = datamodel.Kind_String
nb.scalarNode = NewString(v)
nb.baseNode = NewString(v)
return nil
}
func (nb *anyBuilder) AssignBytes(v []byte) error {
if nb.kind != datamodel.Kind_Invalid {
panic("misuse")
}
nb.kind = datamodel.Kind_Bytes
nb.scalarNode = NewBytes(v)
nb.baseNode = NewBytes(v)
return nil
}
func (nb *anyBuilder) AssignLink(v datamodel.Link) error {
if nb.kind != datamodel.Kind_Invalid {
panic("misuse")
}
nb.kind = datamodel.Kind_Link
nb.scalarNode = NewLink(v)
nb.baseNode = NewLink(v)
return nil
}
func (nb *anyBuilder) AssignNode(v datamodel.Node) error {
if nb.kind != datamodel.Kind_Invalid {
panic("misuse")
}
nb.kind = 99
nb.scalarNode = v
nb.baseNode = v
return nil
}
func (anyBuilder) Prototype() datamodel.NodePrototype {
return Prototype.Any
}

func (nb *anyBuilder) Build() datamodel.Node {
if nb.amender != nil {
return nb.amender.Build()
}
switch nb.kind {
case datamodel.Kind_Invalid:
panic("misuse")
case datamodel.Kind_Map:
return nb.mapBuilder.Build()
return nb.baseNode
case datamodel.Kind_List:
return nb.listBuilder.Build()
return nb.baseNode
case datamodel.Kind_Null:
return datamodel.Null
case datamodel.Kind_Bool:
return nb.scalarNode
return nb.baseNode
case datamodel.Kind_Int:
return nb.scalarNode
return nb.baseNode
case datamodel.Kind_Float:
return nb.scalarNode
return nb.baseNode
case datamodel.Kind_String:
return nb.scalarNode
return nb.baseNode
case datamodel.Kind_Bytes:
return nb.scalarNode
return nb.baseNode
case datamodel.Kind_Link:
return nb.scalarNode
return nb.baseNode
case 99:
return nb.scalarNode
return nb.baseNode
default:
panic("unreachable")
}
}

// -- NodeAmender -->

func (nb *anyBuilder) Transform(path datamodel.Path, transform func(datamodel.Node) (datamodel.NodeAmender, error)) (datamodel.Node, error) {
// If `baseNode` is set and supports amendment, apply the transformation. If it doesn't, and the root is being
// replaced, replace it. If the transformation is for a nested node in a non-amendable recursive object, panic.
if nb.amender != nil {
return nb.amender.Transform(path, transform)
}
// `Transform` should never be called for a non-amendable node
panic("misuse")
}

// -- NodeAssembler -->

// ... oddly enough, we seem to be able to put off implementing this
Expand Down
Loading

0 comments on commit 437510b

Please sign in to comment.