forked from TrueCloudLab/frostfs-node
[#1419] pilorama: Create new nodes in path if needed
Consider a node `{FileName: "dir", Attribute: "xxx"}`. In case we add a new node by path `["dir", "file.txt"]`, create a new intermediate node with a single attribute. `GetByPath` now also considers only nodes with a single attribute while building a path. Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
730f14e4eb
commit
3cc67db083
5 changed files with 58 additions and 16 deletions
|
@ -470,14 +470,10 @@ loop:
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for j := range m.Items {
|
// Internal nodes have exactly one attribute.
|
||||||
if m.Items[j].Key == attr {
|
if len(m.Items) == 1 && m.Items[0].Key == attr && string(m.Items[0].Value) == path[i] {
|
||||||
if string(m.Items[j].Value) == path[i] {
|
curNode = child
|
||||||
curNode = child
|
continue loop
|
||||||
continue loop
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
childKey, _ = c.Next()
|
childKey, _ = c.Next()
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,9 +59,12 @@ func (f *memoryForest) TreeAddByPath(cid cidSDK.ID, treeID string, attr string,
|
||||||
node = lm[j-i].Child
|
node = lm[j-i].Child
|
||||||
s.operations = append(s.operations, lm[j-i])
|
s.operations = append(s.operations, lm[j-i])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mCopy := make([]KeyValue, len(m))
|
||||||
|
copy(mCopy, m)
|
||||||
lm[len(lm)-1] = s.do(&Move{
|
lm[len(lm)-1] = s.do(&Move{
|
||||||
Parent: node,
|
Parent: node,
|
||||||
Meta: Meta{Time: s.timestamp(), Items: m},
|
Meta: Meta{Time: s.timestamp(), Items: mCopy},
|
||||||
Child: s.findSpareID(),
|
Child: s.findSpareID(),
|
||||||
})
|
})
|
||||||
return lm, nil
|
return lm, nil
|
||||||
|
|
|
@ -242,6 +242,44 @@ func testForestTreeAddByPath(t *testing.T, s Forest) {
|
||||||
require.Equal(t, 2, len(lm))
|
require.Equal(t, 2, len(lm))
|
||||||
testMeta(t, s, cid, treeID, lm[0].Child, lm[0].Parent, Meta{Time: lm[0].Time, Items: []KeyValue{{AttributeFilename, []byte("dir")}}})
|
testMeta(t, s, cid, treeID, lm[0].Child, lm[0].Parent, Meta{Time: lm[0].Time, Items: []KeyValue{{AttributeFilename, []byte("dir")}}})
|
||||||
testMeta(t, s, cid, treeID, lm[1].Child, lm[1].Parent, Meta{Time: lm[1].Time, Items: meta})
|
testMeta(t, s, cid, treeID, lm[1].Child, lm[1].Parent, Meta{Time: lm[1].Time, Items: meta})
|
||||||
|
|
||||||
|
t.Run("create internal nodes", func(t *testing.T) {
|
||||||
|
meta[0].Value = []byte("SomeValue")
|
||||||
|
meta[1].Value = []byte("another")
|
||||||
|
lm, err = s.TreeAddByPath(cid, treeID, AttributeFilename, []string{"path"}, meta)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(lm))
|
||||||
|
|
||||||
|
oldMove := lm[0]
|
||||||
|
|
||||||
|
meta[0].Value = []byte("Leaf")
|
||||||
|
meta[1].Value = []byte("file.txt")
|
||||||
|
lm, err = s.TreeAddByPath(cid, treeID, AttributeFilename, []string{"path", "another"}, meta)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(lm))
|
||||||
|
|
||||||
|
testMeta(t, s, cid, treeID, lm[0].Child, lm[0].Parent,
|
||||||
|
Meta{Time: lm[0].Time, Items: []KeyValue{{AttributeFilename, []byte("another")}}})
|
||||||
|
testMeta(t, s, cid, treeID, lm[1].Child, lm[1].Parent, Meta{Time: lm[1].Time, Items: meta})
|
||||||
|
|
||||||
|
require.NotEqual(t, lm[0].Child, oldMove.Child)
|
||||||
|
testMeta(t, s, cid, treeID, oldMove.Child, oldMove.Parent,
|
||||||
|
Meta{Time: oldMove.Time, Items: []KeyValue{
|
||||||
|
{AttributeVersion, []byte("SomeValue")},
|
||||||
|
{AttributeFilename, []byte("another")}}})
|
||||||
|
|
||||||
|
t.Run("get by path", func(t *testing.T) {
|
||||||
|
nodes, err := s.TreeGetByPath(cid, treeID, AttributeFilename, []string{"path", "another"}, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(nodes))
|
||||||
|
require.ElementsMatch(t, []Node{lm[0].Child, oldMove.Child}, nodes)
|
||||||
|
|
||||||
|
nodes, err = s.TreeGetByPath(cid, treeID, AttributeFilename, []string{"path", "another", "file.txt"}, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(nodes))
|
||||||
|
require.Equal(t, lm[1].Child, nodes[0])
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestForest_Apply(t *testing.T) {
|
func TestForest_Apply(t *testing.T) {
|
||||||
|
@ -475,15 +513,18 @@ func testTreeGetByPath(t *testing.T, s Forest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMove(t *testing.T, s Forest, ts int, node, parent Node, cid cidSDK.ID, treeID, filename, version string) {
|
func testMove(t *testing.T, s Forest, ts int, node, parent Node, cid cidSDK.ID, treeID, filename, version string) {
|
||||||
|
items := make([]KeyValue, 1, 2)
|
||||||
|
items[0] = KeyValue{AttributeFilename, []byte(filename)}
|
||||||
|
if version != "" {
|
||||||
|
items = append(items, KeyValue{AttributeVersion, []byte(version)})
|
||||||
|
}
|
||||||
|
|
||||||
require.NoError(t, s.TreeApply(cid, treeID, &Move{
|
require.NoError(t, s.TreeApply(cid, treeID, &Move{
|
||||||
Parent: parent,
|
Parent: parent,
|
||||||
Child: node,
|
Child: node,
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
Time: uint64(ts),
|
Time: uint64(ts),
|
||||||
Items: []KeyValue{
|
Items: items,
|
||||||
{AttributeFilename, []byte(filename)},
|
|
||||||
{AttributeVersion, []byte(version)},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,8 +184,9 @@ loop:
|
||||||
for i := range path {
|
for i := range path {
|
||||||
children := t.childMap[curNode]
|
children := t.childMap[curNode]
|
||||||
for j := range children {
|
for j := range children {
|
||||||
f := t.infoMap[children[j]].Meta.GetAttr(attr)
|
meta := t.infoMap[children[j]].Meta
|
||||||
if string(f) == path[i] {
|
f := meta.GetAttr(attr)
|
||||||
|
if len(meta.Items) == 1 && string(f) == path[i] {
|
||||||
curNode = children[j]
|
curNode = children[j]
|
||||||
continue loop
|
continue loop
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ type Forest interface {
|
||||||
TreeMove(cid cidSDK.ID, treeID string, m *Move) (*LogMove, error)
|
TreeMove(cid cidSDK.ID, treeID string, m *Move) (*LogMove, error)
|
||||||
// TreeAddByPath adds new node in the tree using provided path.
|
// TreeAddByPath adds new node in the tree using provided path.
|
||||||
// The path is constructed by descending from the root using the values of the attr in meta.
|
// The path is constructed by descending from the root using the values of the attr in meta.
|
||||||
|
// Internal nodes in path should have exactly one attribute, otherwise a new node is created.
|
||||||
TreeAddByPath(cid cidSDK.ID, treeID string, attr string, path []string, meta []KeyValue) ([]LogMove, error)
|
TreeAddByPath(cid cidSDK.ID, treeID string, attr string, path []string, meta []KeyValue) ([]LogMove, error)
|
||||||
// TreeApply applies replicated operation from another node.
|
// TreeApply applies replicated operation from another node.
|
||||||
TreeApply(cid cidSDK.ID, treeID string, m *Move) error
|
TreeApply(cid cidSDK.ID, treeID string, m *Move) error
|
||||||
|
|
Loading…
Reference in a new issue