Skip to content

Commit

Permalink
Add RHTNode removal to converter for consistency (#888)
Browse files Browse the repository at this point in the history
This commit addresses the missing `isRemoved` encoding in the RHT.
Similar to other CRDTs like ElementRHT, including tombstone nodes like
`isRemoved` during encoding is crucial. However, the RHT did not
include tombstone nodes in its encoding, leading to inconsistencies in
snapshots.
  • Loading branch information
hackerwins authored Jun 4, 2024
1 parent 4a47984 commit 4612696
Show file tree
Hide file tree
Showing 10 changed files with 1,090 additions and 1,013 deletions.
8 changes: 7 additions & 1 deletion api/converter/converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,14 @@ func TestConverter(t *testing.T) {
})
assert.Equal(t, "<r><p>12</p><p>34</p></r>", root.GetTree("t").ToXML())
root.GetTree("t").EditByPath([]int{0, 1}, []int{1, 1}, nil, 0)

root.GetTree("t").Style(0, 1, map[string]string{"b": "t", "i": "t"})
assert.Equal(t, `<r><p b="t" i="t">14</p></r>`, root.GetTree("t").ToXML())

root.GetTree("t").RemoveStyle(0, 1, []string{"i"})
return nil
}))
assert.Equal(t, "<r><p>14</p></r>", doc.Root().GetTree("t").ToXML())
assert.Equal(t, `<r><p b="t">14</p></r>`, doc.Root().GetTree("t").ToXML())

bytes, err := converter.ObjectToBytes(doc.RootObject())
assert.NoError(t, err)
Expand All @@ -292,5 +297,6 @@ func TestConverter(t *testing.T) {

assert.Equal(t, obj.Get("t").(*crdt.Tree).NodeLen(), doc.Root().GetTree("t").NodeLen())
assert.Equal(t, obj.Get("t").(*crdt.Tree).Root().Len(), doc.Root().GetTree("t").Len())
assert.Equal(t, obj.Get("t").(*crdt.Tree).ToXML(), doc.Root().GetTree("t").ToXML())
})
}
22 changes: 15 additions & 7 deletions api/converter/from_pb.go
Original file line number Diff line number Diff line change
Expand Up @@ -625,19 +625,27 @@ func FromTreeNodesWhenEdit(pbNodes []*api.TreeNodes) ([]*crdt.TreeNode, error) {
return treeNodes, nil
}

func fromRHT(pbRHT map[string]*api.NodeAttr) (*crdt.RHT, error) {
rht := crdt.NewRHT()
for k, pbAttr := range pbRHT {
updatedAt, err := fromTimeTicket(pbAttr.UpdatedAt)
if err != nil {
return nil, err
}
rht.SetInternal(k, pbAttr.Value, updatedAt, pbAttr.IsRemoved)
}
return rht, nil
}

func fromTreeNode(pbNode *api.TreeNode) (*crdt.TreeNode, error) {
id, err := fromTreeNodeID(pbNode.Id)
if err != nil {
return nil, err
}

attrs := crdt.NewRHT()
for k, pbAttr := range pbNode.Attributes {
updatedAt, err := fromTimeTicket(pbAttr.UpdatedAt)
if err != nil {
return nil, err
}
attrs.Set(k, pbAttr.Value, updatedAt)
attrs, err := fromRHT(pbNode.Attributes)
if err != nil {
return nil, err
}

node := crdt.NewTreeNode(
Expand Down
19 changes: 12 additions & 7 deletions api/converter/to_bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,25 +297,30 @@ func ToTreeNodesWhenEdit(treeNodes []*crdt.TreeNode) []*api.TreeNodes {
return pbTreeNodes
}

func toTreeNode(treeNode *crdt.TreeNode, depth int) *api.TreeNode {
var attrs map[string]*api.NodeAttr
if treeNode.Attrs != nil {
attrs = make(map[string]*api.NodeAttr)
for _, node := range treeNode.Attrs.Nodes() {
attrs[node.Key()] = &api.NodeAttr{
func toRHT(rht *crdt.RHT) map[string]*api.NodeAttr {
var pbAttrs map[string]*api.NodeAttr
if rht != nil {
pbAttrs = make(map[string]*api.NodeAttr)
for _, node := range rht.Nodes() {
pbAttrs[node.Key()] = &api.NodeAttr{
Value: node.Value(),
UpdatedAt: ToTimeTicket(node.UpdatedAt()),
IsRemoved: node.IsRemoved(),
}
}
}

return pbAttrs
}

func toTreeNode(treeNode *crdt.TreeNode, depth int) *api.TreeNode {
pbNode := &api.TreeNode{
Id: toTreeNodeID(treeNode.ID()),
Type: treeNode.Type(),
Value: treeNode.Value,
RemovedAt: ToTimeTicket(treeNode.RemovedAt()),
Depth: int32(depth),
Attributes: attrs,
Attributes: toRHT(treeNode.Attrs),
}

if treeNode.InsPrevID != nil {
Expand Down
Loading

0 comments on commit 4612696

Please sign in to comment.