~whereswaldon/forest-go

d91d674e5b16b22200d662d2d24fcb3999508377 — Chris Waldon 2 years ago 3d63067 + aba8ba9
Merge branch 'list-store'
2 files changed, 90 insertions(+), 2 deletions(-)

M store.go
M store_test.go
M store.go => store.go +43 -0
@@ 2,6 2,7 @@ package forest

import (
	"fmt"
	"sort"

	"git.sr.ht/~whereswaldon/forest-go/fields"
)


@@ 15,6 16,7 @@ type Store interface {
	GetConversation(communityID, conversationID *fields.QualifiedHash) (Node, bool, error)
	GetReply(communityID, conversationID, replyID *fields.QualifiedHash) (Node, bool, error)
	Children(*fields.QualifiedHash) ([]*fields.QualifiedHash, error)
	Recent(nodeType fields.NodeType, quantity int) ([]Node, error)
	Add(Node) error
}



@@ 115,6 117,43 @@ func (m *MemoryStore) AddID(id string, node Node) error {
	return nil
}

// Recent returns a slice of len `quantity` (or fewer) nodes of the given type.
// These nodes are the most recent (by creation time) nodes of that type known
// to the store.
func (m *MemoryStore) Recent(nodeType fields.NodeType, quantity int) ([]Node, error) {
	// highly inefficient implementation, but it should work for now
	candidates := make([]Node, 0, quantity)
	for _, node := range m.Items {
		switch n := node.(type) {
		case *Identity:
			if nodeType == fields.NodeTypeIdentity {
				candidates = append(candidates, n)
				sort.SliceStable(candidates, func(i, j int) bool {
					return candidates[i].(*Identity).Created > candidates[j].(*Identity).Created
				})
			}
		case *Community:
			if nodeType == fields.NodeTypeCommunity {
				candidates = append(candidates, n)
				sort.SliceStable(candidates, func(i, j int) bool {
					return candidates[i].(*Community).Created > candidates[j].(*Community).Created
				})
			}
		case *Reply:
			if nodeType == fields.NodeTypeReply {
				candidates = append(candidates, n)
				sort.SliceStable(candidates, func(i, j int) bool {
					return candidates[i].(*Reply).Created > candidates[j].(*Reply).Created
				})
			}
		}
	}
	if len(candidates) > quantity {
		candidates = candidates[:quantity]
	}
	return candidates, nil
}

// CacheStore combines two other stores into one logical store. It is
// useful when store implementations have different performance
// characteristics and one is dramatically faster than the other. Once


@@ 219,3 258,7 @@ func (m *CacheStore) GetReply(communityID, conversationID, replyID *fields.Quali
func (m *CacheStore) Children(id *fields.QualifiedHash) ([]*fields.QualifiedHash, error) {
	return m.Back.Children(id)
}

func (m *CacheStore) Recent(nodeType fields.NodeType, quantity int) ([]Node, error) {
	return m.Back.Recent(nodeType, quantity)
}

M store_test.go => store_test.go +47 -2
@@ 84,7 84,7 @@ func testStandardStoreInterface(t *testing.T, s forest.Store, storeImplName stri
		}
	}

    // map nodes to the children that they ought to have within the store
	// map nodes to the children that they ought to have within the store
	nodesToChildren := []struct {
		forest.Node
		children []*fields.QualifiedHash


@@ 94,7 94,7 @@ func testStandardStoreInterface(t *testing.T, s forest.Store, storeImplName stri
		{reply, []*fields.QualifiedHash{}},
	}

    // check each node has its proper children
	// check each node has its proper children
	for _, childConfig := range nodesToChildren {
		if children, err := s.Children(childConfig.ID()); err != nil {
			t.Errorf("%s should not error fetching children of %v", storeImplName, childConfig.ID())


@@ 106,6 106,51 @@ func testStandardStoreInterface(t *testing.T, s forest.Store, storeImplName stri
			}
		}
	}

	// add some more nodes so that we can test the Recent method
	identity2, _, community2, reply2 := MakeReplyOrSkip(t)
	for _, i := range []forest.Node{identity2, community2, reply2} {
		if err := s.Add(i); err != nil {
			t.Errorf("%s Add() should not err on Add(): %s", storeImplName, err)
		}
	}
	// try recent on each node type and ensure that it returns the right
	// number and order of results
	type recentRun struct {
		fields.NodeType
		atZero forest.Node
		atOne  forest.Node
	}
	for _, run := range []recentRun{
		{fields.NodeTypeIdentity, identity2, identity},
		{fields.NodeTypeCommunity, community2, community},
		{fields.NodeTypeReply, reply2, reply},
	} {
		recentNodes, err := s.Recent(run.NodeType, 1)
		if err != nil {
			t.Errorf("Recent failed on valid input: %v", err)
		} else if len(recentNodes) < 1 {
			t.Errorf("Recent on store with data returned too few results")
		} else if !recentNodes[0].Equals(run.atZero) {
			t.Errorf("Expected most recent node to be the newly created one")
		}
		recentNodes, err = s.Recent(run.NodeType, 2)
		if err != nil {
			t.Errorf("Recent failed on valid input: %v", err)
		} else if len(recentNodes) < 2 {
			t.Errorf("Recent on store with data returned too few results")
		} else if !recentNodes[0].Equals(run.atZero) {
			t.Errorf("Expected most recent node to be the newly created one")
		} else if !recentNodes[1].Equals(run.atOne) {
			t.Errorf("Expected first node to be the older one")
		}
		recentNodes, err = s.Recent(run.NodeType, 3)
		if err != nil {
			t.Errorf("Recent failed on valid input: %v", err)
		} else if len(recentNodes) > 2 {
			t.Errorf("Recent on store with only two matching nodes returned more than 2 results")
		}
	}
}

func containsID(ids []*fields.QualifiedHash, id *fields.QualifiedHash) bool {