Merge pull request #19 from fkautz/pr_out_in_memory_object_storage_now_uses_patricia_tree

In memory object storage now uses patricia tree
This commit is contained in:
Anand Babu (AB) Periasamy 2014-11-08 00:11:49 -08:00
commit cbce7fd66f
8 changed files with 1579 additions and 7 deletions

5
Godeps/Godeps.json generated
View File

@ -18,6 +18,11 @@
"ImportPath": "github.com/gorilla/mux",
"Rev": "e444e69cbd2e2e3e0749a2f3c717cec491552bbf"
},
{
"ImportPath": "github.com/tchap/go-patricia/patricia",
"Comment": "v1.0.1",
"Rev": "f64d0a63cd3363481c898faa9339de04d12213f9"
},
{
"ImportPath": "gopkg.in/check.v1",
"Rev": "64131543e7896d5bcc6bd5a76287eb75ea96c673"

View File

@ -0,0 +1,230 @@
// Copyright (c) 2014 The go-patricia AUTHORS
//
// Use of this source code is governed by The MIT License
// that can be found in the LICENSE file.
package patricia
// Max prefix length that is kept in a single trie node.
var MaxPrefixPerNode = 10
// Max children to keep in a node in the sparse mode.
const MaxChildrenPerSparseNode = 8
type childList interface {
length() int
head() *Trie
add(child *Trie) childList
replace(b byte, child *Trie)
remove(child *Trie)
next(b byte) *Trie
walk(prefix *Prefix, visitor VisitorFunc) error
}
type sparseChildList struct {
children []*Trie
}
func newSparseChildList() childList {
return &sparseChildList{
children: make([]*Trie, 0, MaxChildrenPerSparseNode),
}
}
func (list *sparseChildList) length() int {
return len(list.children)
}
func (list *sparseChildList) head() *Trie {
return list.children[0]
}
func (list *sparseChildList) add(child *Trie) childList {
// Search for an empty spot and insert the child if possible.
if len(list.children) != cap(list.children) {
list.children = append(list.children, child)
return list
}
// Otherwise we have to transform to the dense list type.
return newDenseChildList(list, child)
}
func (list *sparseChildList) replace(b byte, child *Trie) {
// Seek the child and replace it.
for i, node := range list.children {
if node.prefix[0] == b {
list.children[i] = child
return
}
}
}
func (list *sparseChildList) remove(child *Trie) {
for i, node := range list.children {
if node.prefix[0] == child.prefix[0] {
list.children = append(list.children[:i], list.children[i+1:]...)
return
}
}
// This is not supposed to be reached.
panic("removing non-existent child")
}
func (list *sparseChildList) next(b byte) *Trie {
for _, child := range list.children {
if child.prefix[0] == b {
return child
}
}
return nil
}
func (list *sparseChildList) walk(prefix *Prefix, visitor VisitorFunc) error {
for _, child := range list.children {
*prefix = append(*prefix, child.prefix...)
if child.item != nil {
err := visitor(*prefix, child.item)
if err != nil {
if err == SkipSubtree {
*prefix = (*prefix)[:len(*prefix)-len(child.prefix)]
continue
}
*prefix = (*prefix)[:len(*prefix)-len(child.prefix)]
return err
}
}
err := child.children.walk(prefix, visitor)
*prefix = (*prefix)[:len(*prefix)-len(child.prefix)]
if err != nil {
return err
}
}
return nil
}
type denseChildList struct {
min int
max int
children []*Trie
}
func newDenseChildList(list *sparseChildList, child *Trie) childList {
var (
min int = 255
max int = 0
)
for _, child := range list.children {
b := int(child.prefix[0])
if b < min {
min = b
}
if b > max {
max = b
}
}
b := int(child.prefix[0])
if b < min {
min = b
}
if b > max {
max = b
}
children := make([]*Trie, max-min+1)
for _, child := range list.children {
children[int(child.prefix[0])-min] = child
}
children[int(child.prefix[0])-min] = child
return &denseChildList{min, max, children}
}
func (list *denseChildList) length() int {
return list.max - list.min + 1
}
func (list *denseChildList) head() *Trie {
return list.children[0]
}
func (list *denseChildList) add(child *Trie) childList {
b := int(child.prefix[0])
switch {
case list.min <= b && b <= list.max:
if list.children[b-list.min] != nil {
panic("dense child list collision detected")
}
list.children[b-list.min] = child
case b < list.min:
children := make([]*Trie, list.max-b+1)
children[0] = child
copy(children[list.min-b:], list.children)
list.children = children
list.min = b
default: // b > list.max
children := make([]*Trie, b-list.min+1)
children[b-list.min] = child
copy(children, list.children)
list.children = children
list.max = b
}
return list
}
func (list *denseChildList) replace(b byte, child *Trie) {
list.children[int(b)-list.min] = nil
list.children[int(child.prefix[0])-list.min] = child
}
func (list *denseChildList) remove(child *Trie) {
i := int(child.prefix[0]) - list.min
if list.children[i] == nil {
// This is not supposed to be reached.
panic("removing non-existent child")
}
list.children[i] = nil
}
func (list *denseChildList) next(b byte) *Trie {
i := int(b)
if i < list.min || list.max < i {
return nil
}
return list.children[i-list.min]
}
func (list *denseChildList) walk(prefix *Prefix, visitor VisitorFunc) error {
for _, child := range list.children {
if child == nil {
continue
}
*prefix = append(*prefix, child.prefix...)
if child.item != nil {
if err := visitor(*prefix, child.item); err != nil {
if err == SkipSubtree {
*prefix = (*prefix)[:len(*prefix)-len(child.prefix)]
continue
}
*prefix = (*prefix)[:len(*prefix)-len(child.prefix)]
return err
}
}
err := child.children.walk(prefix, visitor)
*prefix = (*prefix)[:len(*prefix)-len(child.prefix)]
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,432 @@
// Copyright (c) 2014 The go-patricia AUTHORS
//
// Use of this source code is governed by The MIT License
// that can be found in the LICENSE file.
package patricia
import (
"errors"
)
//------------------------------------------------------------------------------
// Trie
//------------------------------------------------------------------------------
type (
Prefix []byte
Item interface{}
VisitorFunc func(prefix Prefix, item Item) error
)
// Trie is a generic patricia trie that allows fast retrieval of items by prefix.
// and other funky stuff.
//
// Trie is not thread-safe.
type Trie struct {
prefix Prefix
item Item
children childList
}
// Public API ------------------------------------------------------------------
// Trie constructor.
func NewTrie() *Trie {
return &Trie{
children: newSparseChildList(),
}
}
// Item returns the item stored in the root of this trie.
func (trie *Trie) Item() Item {
return trie.item
}
// Insert inserts a new item into the trie using the given prefix. Insert does
// not replace existing items. It returns false if an item was already in place.
func (trie *Trie) Insert(key Prefix, item Item) (inserted bool) {
return trie.put(key, item, false)
}
// Set works much like Insert, but it always sets the item, possibly replacing
// the item previously inserted.
func (trie *Trie) Set(key Prefix, item Item) {
trie.put(key, item, true)
}
// Get returns the item located at key.
//
// This method is a bit dangerous, because Get can as well end up in an internal
// node that is not really representing any user-defined value. So when nil is
// a valid value being used, it is not possible to tell if the value was inserted
// into the tree by the user or not. A possible workaround for this is not to use
// nil interface as a valid value, even using zero value of any type is enough
// to prevent this bad behaviour.
func (trie *Trie) Get(key Prefix) (item Item) {
_, node, found, leftover := trie.findSubtree(key)
if !found || len(leftover) != 0 {
return nil
}
return node.item
}
// Match returns what Get(prefix) != nil would return. The same warning as for
// Get applies here as well.
func (trie *Trie) Match(prefix Prefix) (matchedExactly bool) {
return trie.Get(prefix) != nil
}
// MatchSubtree returns true when there is a subtree representing extensions
// to key, that is if there are any keys in the tree which have key as prefix.
func (trie *Trie) MatchSubtree(key Prefix) (matched bool) {
_, _, matched, _ = trie.findSubtree(key)
return
}
// Visit calls visitor on every node containing a non-nil item.
//
// If an error is returned from visitor, the function stops visiting the tree
// and returns that error, unless it is a special error - SkipSubtree. In that
// case Visit skips the subtree represented by the current node and continues
// elsewhere.
func (trie *Trie) Visit(visitor VisitorFunc) error {
return trie.walk(nil, visitor)
}
// VisitSubtree works much like Visit, but it only visits nodes matching prefix.
func (trie *Trie) VisitSubtree(prefix Prefix, visitor VisitorFunc) error {
// Nil prefix not allowed.
if prefix == nil {
panic(ErrNilPrefix)
}
// Empty trie must be handled explicitly.
if trie.prefix == nil {
return nil
}
// Locate the relevant subtree.
_, root, found, leftover := trie.findSubtree(prefix)
if !found {
return nil
}
prefix = append(prefix, leftover...)
// Visit it.
return root.walk(prefix, visitor)
}
// VisitPrefixes visits only nodes that represent prefixes of key.
// To say the obvious, returning SkipSubtree from visitor makes no sense here.
func (trie *Trie) VisitPrefixes(key Prefix, visitor VisitorFunc) error {
// Nil key not allowed.
if key == nil {
panic(ErrNilPrefix)
}
// Empty trie must be handled explicitly.
if trie.prefix == nil {
return nil
}
// Walk the path matching key prefixes.
node := trie
prefix := key
offset := 0
for {
// Compute what part of prefix matches.
common := node.longestCommonPrefixLength(key)
key = key[common:]
offset += common
// Partial match means that there is no subtree matching prefix.
if common < len(node.prefix) {
return nil
}
// Call the visitor.
if item := node.item; item != nil {
if err := visitor(prefix[:offset], item); err != nil {
return err
}
}
if len(key) == 0 {
// This node represents key, we are finished.
return nil
}
// There is some key suffix left, move to the children.
child := node.children.next(key[0])
if child == nil {
// There is nowhere to continue, return.
return nil
}
node = child
}
}
// Delete deletes the item represented by the given prefix.
//
// True is returned if the matching node was found and deleted.
func (trie *Trie) Delete(key Prefix) (deleted bool) {
// Nil prefix not allowed.
if key == nil {
panic(ErrNilPrefix)
}
// Empty trie must be handled explicitly.
if trie.prefix == nil {
return false
}
// Find the relevant node.
parent, node, _, leftover := trie.findSubtree(key)
if len(leftover) != 0 {
return false
}
// If the item is already set to nil, there is nothing to do.
if node.item == nil {
return false
}
// Delete the item.
node.item = nil
// Compact since that might be possible now.
if compacted := node.compact(); compacted != node {
if parent == nil {
*node = *compacted
} else {
parent.children.replace(node.prefix[0], compacted)
*parent = *parent.compact()
}
}
return true
}
// DeleteSubtree finds the subtree exactly matching prefix and deletes it.
//
// True is returned if the subtree was found and deleted.
func (trie *Trie) DeleteSubtree(prefix Prefix) (deleted bool) {
// Nil prefix not allowed.
if prefix == nil {
panic(ErrNilPrefix)
}
// Empty trie must be handled explicitly.
if trie.prefix == nil {
return false
}
// Locate the relevant subtree.
parent, root, found, _ := trie.findSubtree(prefix)
if !found {
return false
}
// If we are in the root of the trie, reset the trie.
if parent == nil {
root.prefix = nil
root.children = newSparseChildList()
return true
}
// Otherwise remove the root node from its parent.
parent.children.remove(root)
return true
}
// Internal helper methods -----------------------------------------------------
func (trie *Trie) put(key Prefix, item Item, replace bool) (inserted bool) {
// Nil prefix not allowed.
if key == nil {
panic(ErrNilPrefix)
}
var (
common int
node *Trie = trie
child *Trie
)
if node.prefix == nil {
if len(key) <= MaxPrefixPerNode {
node.prefix = key
goto InsertItem
}
node.prefix = key[:MaxPrefixPerNode]
key = key[MaxPrefixPerNode:]
goto AppendChild
}
for {
// Compute the longest common prefix length.
common = node.longestCommonPrefixLength(key)
key = key[common:]
// Only a part matches, split.
if common < len(node.prefix) {
goto SplitPrefix
}
// common == len(node.prefix) since never (common > len(node.prefix))
// common == len(former key) <-> 0 == len(key)
// -> former key == node.prefix
if len(key) == 0 {
goto InsertItem
}
// Check children for matching prefix.
child = node.children.next(key[0])
if child == nil {
goto AppendChild
}
node = child
}
SplitPrefix:
// Split the prefix if necessary.
child = new(Trie)
*child = *node
*node = *NewTrie()
node.prefix = child.prefix[:common]
child.prefix = child.prefix[common:]
child = child.compact()
node.children = node.children.add(child)
AppendChild:
// Keep appending children until whole prefix is inserted.
// This loop starts with empty node.prefix that needs to be filled.
for len(key) != 0 {
child := NewTrie()
if len(key) <= MaxPrefixPerNode {
child.prefix = key
node.children = node.children.add(child)
node = child
goto InsertItem
} else {
child.prefix = key[:MaxPrefixPerNode]
key = key[MaxPrefixPerNode:]
node.children = node.children.add(child)
node = child
}
}
InsertItem:
// Try to insert the item if possible.
if replace || node.item == nil {
node.item = item
return true
}
return false
}
func (trie *Trie) compact() *Trie {
// Only a node with a single child can be compacted.
if trie.children.length() != 1 {
return trie
}
child := trie.children.head()
// If any item is set, we cannot compact since we want to retain
// the ability to do searching by key. This makes compaction less usable,
// but that simply cannot be avoided.
if trie.item != nil || child.item != nil {
return trie
}
// Make sure the combined prefixes fit into a single node.
if len(trie.prefix)+len(child.prefix) > MaxPrefixPerNode {
return trie
}
// Concatenate the prefixes, move the items.
child.prefix = append(trie.prefix, child.prefix...)
if trie.item != nil {
child.item = trie.item
}
return child
}
func (trie *Trie) findSubtree(prefix Prefix) (parent *Trie, root *Trie, found bool, leftover Prefix) {
// Find the subtree matching prefix.
root = trie
for {
// Compute what part of prefix matches.
common := root.longestCommonPrefixLength(prefix)
prefix = prefix[common:]
// We used up the whole prefix, subtree found.
if len(prefix) == 0 {
found = true
leftover = root.prefix[common:]
return
}
// Partial match means that there is no subtree matching prefix.
if common < len(root.prefix) {
leftover = root.prefix[common:]
return
}
// There is some prefix left, move to the children.
child := root.children.next(prefix[0])
if child == nil {
// There is nowhere to continue, there is no subtree matching prefix.
return
}
parent = root
root = child
}
}
func (trie *Trie) walk(actualRootPrefix Prefix, visitor VisitorFunc) error {
var prefix Prefix
// Allocate a bit more space for prefix at the beginning.
if actualRootPrefix == nil {
prefix = make(Prefix, 32+len(trie.prefix))
copy(prefix, trie.prefix)
prefix = prefix[:len(trie.prefix)]
} else {
prefix = make(Prefix, 32+len(actualRootPrefix))
copy(prefix, actualRootPrefix)
prefix = prefix[:len(actualRootPrefix)]
}
// Visit the root first. Not that this works for empty trie as well since
// in that case item == nil && len(children) == 0.
if trie.item != nil {
if err := visitor(prefix, trie.item); err != nil {
if err == SkipSubtree {
return nil
}
return err
}
}
// Then continue to the children.
return trie.children.walk(&prefix, visitor)
}
func (trie *Trie) longestCommonPrefixLength(prefix Prefix) (i int) {
for ; i < len(prefix) && i < len(trie.prefix) && prefix[i] == trie.prefix[i]; i++ {
}
return
}
// Errors ----------------------------------------------------------------------
var (
SkipSubtree = errors.New("Skip this subtree")
ErrNilPrefix = errors.New("Nil prefix passed into a method call")
)

View File

@ -0,0 +1,161 @@
// Copyright (c) 2014 The go-patricia AUTHORS
//
// Use of this source code is governed by The MIT License
// that can be found in the LICENSE file.
package patricia
import (
"testing"
)
// Tests -----------------------------------------------------------------------
func TestTrie_InsertDense(t *testing.T) {
trie := NewTrie()
data := []testData{
{"aba", 0, success},
{"abb", 1, success},
{"abc", 2, success},
{"abd", 3, success},
{"abe", 4, success},
{"abf", 5, success},
{"abg", 6, success},
{"abh", 7, success},
{"abi", 8, success},
{"abj", 9, success},
{"abk", 0, success},
{"abl", 1, success},
{"abm", 2, success},
{"abn", 3, success},
{"abo", 4, success},
{"abp", 5, success},
{"abq", 6, success},
{"abr", 7, success},
{"abs", 8, success},
{"abt", 9, success},
{"abu", 0, success},
{"abv", 1, success},
{"abw", 2, success},
{"abx", 3, success},
{"aby", 4, success},
{"abz", 5, success},
}
for _, v := range data {
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal {
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
}
func TestTrie_InsertDensePreceeding(t *testing.T) {
trie := NewTrie()
start := byte(70)
// create a dense node
for i := byte(0); i <= MaxChildrenPerSparseNode; i++ {
if !trie.Insert(Prefix([]byte{start + i}), true) {
t.Errorf("insert failed, prefix=%v", start+i)
}
}
// insert some preceeding keys
for i := byte(1); i < start; i *= i + 1 {
if !trie.Insert(Prefix([]byte{start - i}), true) {
t.Errorf("insert failed, prefix=%v", start-i)
}
}
}
func TestTrie_InsertDenseDuplicatePrefixes(t *testing.T) {
trie := NewTrie()
data := []testData{
{"aba", 0, success},
{"abb", 1, success},
{"abc", 2, success},
{"abd", 3, success},
{"abe", 4, success},
{"abf", 5, success},
{"abg", 6, success},
{"abh", 7, success},
{"abi", 8, success},
{"abj", 9, success},
{"abk", 0, success},
{"abl", 1, success},
{"abm", 2, success},
{"abn", 3, success},
{"abo", 4, success},
{"abp", 5, success},
{"abq", 6, success},
{"abr", 7, success},
{"abs", 8, success},
{"abt", 9, success},
{"abu", 0, success},
{"abv", 1, success},
{"abw", 2, success},
{"abx", 3, success},
{"aby", 4, success},
{"abz", 5, success},
{"aba", 0, failure},
{"abb", 1, failure},
{"abc", 2, failure},
{"abd", 3, failure},
{"abe", 4, failure},
}
for _, v := range data {
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal {
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
}
func TestTrie_DeleteDense(t *testing.T) {
trie := NewTrie()
data := []testData{
{"aba", 0, success},
{"abb", 1, success},
{"abc", 2, success},
{"abd", 3, success},
{"abe", 4, success},
{"abf", 5, success},
{"abg", 6, success},
{"abh", 7, success},
{"abi", 8, success},
{"abj", 9, success},
{"abk", 0, success},
{"abl", 1, success},
{"abm", 2, success},
{"abn", 3, success},
{"abo", 4, success},
{"abp", 5, success},
{"abq", 6, success},
{"abr", 7, success},
{"abs", 8, success},
{"abt", 9, success},
{"abu", 0, success},
{"abv", 1, success},
{"abw", 2, success},
{"abx", 3, success},
{"aby", 4, success},
{"abz", 5, success},
}
for _, v := range data {
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal {
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
for _, v := range data {
t.Logf("DELETE word=%v, success=%v", v.key, v.retVal)
if ok := trie.Delete([]byte(v.key)); ok != v.retVal {
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
}

View File

@ -0,0 +1,659 @@
// Copyright (c) 2014 The go-patricia AUTHORS
//
// Use of this source code is governed by The MIT License
// that can be found in the LICENSE file.
package patricia
import (
"bytes"
"errors"
"fmt"
"strings"
"testing"
)
const (
success = true
failure = false
)
type testData struct {
key string
value interface{}
retVal bool
}
// Tests -----------------------------------------------------------------------
func TestTrie_InsertDifferentPrefixes(t *testing.T) {
trie := NewTrie()
data := []testData{
{"Pepaneeeeeeeeeeeeee", "Pepan Zdepan", success},
{"Honzooooooooooooooo", "Honza Novak", success},
{"Jenikuuuuuuuuuuuuuu", "Jenik Poustevnicek", success},
}
for _, v := range data {
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal {
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
}
func TestTrie_InsertDuplicatePrefixes(t *testing.T) {
trie := NewTrie()
data := []testData{
{"Pepan", "Pepan Zdepan", success},
{"Pepan", "Pepan Zdepan", failure},
}
for _, v := range data {
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal {
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
}
func TestTrie_InsertVariousPrefixes(t *testing.T) {
trie := NewTrie()
data := []testData{
{"Pepan", "Pepan Zdepan", success},
{"Pepin", "Pepin Omacka", success},
{"Honza", "Honza Novak", success},
{"Jenik", "Jenik Poustevnicek", success},
{"Pepan", "Pepan Dupan", failure},
{"Karel", "Karel Pekar", success},
{"Jenik", "Jenik Poustevnicek", failure},
{"Pepanek", "Pepanek Zemlicka", success},
}
for _, v := range data {
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal {
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
}
func TestTrie_InsertAndMatchPrefix(t *testing.T) {
trie := NewTrie()
t.Log("INSERT prefix=by week")
trie.Insert(Prefix("by week"), 2)
t.Log("INSERT prefix=by")
trie.Insert(Prefix("by"), 1)
if !trie.Match(Prefix("by")) {
t.Error("MATCH prefix=by, expected=true, got=false")
}
}
func TestTrie_SetGet(t *testing.T) {
trie := NewTrie()
data := []testData{
{"Pepan", "Pepan Zdepan", success},
{"Pepin", "Pepin Omacka", success},
{"Honza", "Honza Novak", success},
{"Jenik", "Jenik Poustevnicek", success},
{"Pepan", "Pepan Dupan", failure},
{"Karel", "Karel Pekar", success},
{"Jenik", "Jenik Poustevnicek", failure},
{"Pepanek", "Pepanek Zemlicka", success},
}
for _, v := range data {
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal {
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
for _, v := range data {
t.Logf("SET %q to 10", v.key)
trie.Set(Prefix(v.key), 10)
}
for _, v := range data {
value := trie.Get(Prefix(v.key))
t.Logf("GET %q => %v", v.key, value)
if value.(int) != 10 {
t.Errorf("Unexpected return value, != 10", value)
}
}
if value := trie.Get(Prefix("random crap")); value != nil {
t.Errorf("Unexpected return value, %v != <nil>", value)
}
}
func TestTrie_Match(t *testing.T) {
trie := NewTrie()
data := []testData{
{"Pepan", "Pepan Zdepan", success},
{"Pepin", "Pepin Omacka", success},
{"Honza", "Honza Novak", success},
{"Jenik", "Jenik Poustevnicek", success},
{"Pepan", "Pepan Dupan", failure},
{"Karel", "Karel Pekar", success},
{"Jenik", "Jenik Poustevnicek", failure},
{"Pepanek", "Pepanek Zemlicka", success},
}
for _, v := range data {
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal {
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
for _, v := range data {
matched := trie.Match(Prefix(v.key))
t.Logf("MATCH %q => %v", v.key, matched)
if !matched {
t.Errorf("Inserted key %q was not matched", v.key)
}
}
if trie.Match(Prefix("random crap")) {
t.Errorf("Key that was not inserted matched: %q", "random crap")
}
}
func TestTrie_MatchFalsePositive(t *testing.T) {
trie := NewTrie()
if ok := trie.Insert(Prefix("A"), 1); !ok {
t.Fatal("INSERT prefix=A, item=1 not ok")
}
resultMatchSubtree := trie.MatchSubtree(Prefix("A extra"))
resultMatch := trie.Match(Prefix("A extra"))
if resultMatchSubtree != false {
t.Error("MatchSubtree returned false positive")
}
if resultMatch != false {
t.Error("Match returned false positive")
}
}
func TestTrie_MatchSubtree(t *testing.T) {
trie := NewTrie()
data := []testData{
{"Pepan", "Pepan Zdepan", success},
{"Pepin", "Pepin Omacka", success},
{"Honza", "Honza Novak", success},
{"Jenik", "Jenik Poustevnicek", success},
{"Pepan", "Pepan Dupan", failure},
{"Karel", "Karel Pekar", success},
{"Jenik", "Jenik Poustevnicek", failure},
{"Pepanek", "Pepanek Zemlicka", success},
}
for _, v := range data {
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal {
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
for _, v := range data {
key := Prefix(v.key[:3])
matched := trie.MatchSubtree(key)
t.Logf("MATCH_SUBTREE %q => %v", key, matched)
if !matched {
t.Errorf("Subtree %q was not matched", v.key)
}
}
}
func TestTrie_Visit(t *testing.T) {
trie := NewTrie()
data := []testData{
{"Pepa", 0, success},
{"Pepa Zdepa", 1, success},
{"Pepa Kuchar", 2, success},
{"Honza", 3, success},
{"Jenik", 4, success},
}
for _, v := range data {
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal {
t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
if err := trie.Visit(func(prefix Prefix, item Item) error {
name := data[item.(int)].key
t.Logf("VISITING prefix=%q, item=%v", prefix, item)
if !strings.HasPrefix(string(prefix), name) {
t.Errorf("Unexpected prefix encountered, %q not a prefix of %q", prefix, name)
}
return nil
}); err != nil {
t.Fatal(err)
}
}
func TestTrie_VisitSkipSubtree(t *testing.T) {
trie := NewTrie()
data := []testData{
{"Pepa", 0, success},
{"Pepa Zdepa", 1, success},
{"Pepa Kuchar", 2, success},
{"Honza", 3, success},
{"Jenik", 4, success},
}
for _, v := range data {
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal {
t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
if err := trie.Visit(func(prefix Prefix, item Item) error {
t.Logf("VISITING prefix=%q, item=%v", prefix, item)
if item.(int) == 0 {
t.Logf("SKIP %q", prefix)
return SkipSubtree
}
if strings.HasPrefix(string(prefix), "Pepa") {
t.Errorf("Unexpected prefix encountered, %q", prefix)
}
return nil
}); err != nil {
t.Fatal(err)
}
}
func TestTrie_VisitReturnError(t *testing.T) {
trie := NewTrie()
data := []testData{
{"Pepa", 0, success},
{"Pepa Zdepa", 1, success},
{"Pepa Kuchar", 2, success},
{"Honza", 3, success},
{"Jenik", 4, success},
}
for _, v := range data {
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal {
t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
someErr := errors.New("Something exploded")
if err := trie.Visit(func(prefix Prefix, item Item) error {
t.Logf("VISITING prefix=%q, item=%v", prefix, item)
if item.(int) == 0 {
return someErr
}
if item.(int) != 0 {
t.Errorf("Unexpected prefix encountered, %q", prefix)
}
return nil
}); err != nil && err != someErr {
t.Fatal(err)
}
}
func TestTrie_VisitSubtree(t *testing.T) {
trie := NewTrie()
data := []testData{
{"Pepa", 0, success},
{"Pepa Zdepa", 1, success},
{"Pepa Kuchar", 2, success},
{"Honza", 3, success},
{"Jenik", 4, success},
}
for _, v := range data {
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal {
t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
var counter int
subtreePrefix := []byte("Pep")
t.Log("VISIT Pep")
if err := trie.VisitSubtree(subtreePrefix, func(prefix Prefix, item Item) error {
t.Logf("VISITING prefix=%q, item=%v", prefix, item)
if !bytes.HasPrefix(prefix, subtreePrefix) {
t.Errorf("Unexpected prefix encountered, %q does not extend %q",
prefix, subtreePrefix)
}
if len(prefix) > len(data[item.(int)].key) {
t.Fatalf("Something is rather fishy here, prefix=%q", prefix)
}
counter++
return nil
}); err != nil {
t.Fatal(err)
}
if counter != 3 {
t.Error("Unexpected number of nodes visited")
}
}
func TestTrie_VisitPrefixes(t *testing.T) {
trie := NewTrie()
data := []testData{
{"P", 0, success},
{"Pe", 1, success},
{"Pep", 2, success},
{"Pepa", 3, success},
{"Pepa Zdepa", 4, success},
{"Pepa Kuchar", 5, success},
{"Honza", 6, success},
{"Jenik", 7, success},
}
for _, v := range data {
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal {
t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
var counter int
word := []byte("Pepa")
if err := trie.VisitPrefixes(word, func(prefix Prefix, item Item) error {
t.Logf("VISITING prefix=%q, item=%v", prefix, item)
if !bytes.HasPrefix(word, prefix) {
t.Errorf("Unexpected prefix encountered, %q is not a prefix of %q",
prefix, word)
}
counter++
return nil
}); err != nil {
t.Fatal(err)
}
if counter != 4 {
t.Error("Unexpected number of nodes visited")
}
}
func TestParticiaTrie_Delete(t *testing.T) {
trie := NewTrie()
data := []testData{
{"Pepan", "Pepan Zdepan", success},
{"Honza", "Honza Novak", success},
{"Jenik", "Jenik Poustevnicek", success},
}
for _, v := range data {
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal {
t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
for _, v := range data {
t.Logf("DELETE word=%v, success=%v", v.key, v.retVal)
if ok := trie.Delete([]byte(v.key)); ok != v.retVal {
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
}
func TestParticiaTrie_DeleteNonExistent(t *testing.T) {
trie := NewTrie()
insertData := []testData{
{"Pepan", "Pepan Zdepan", success},
{"Honza", "Honza Novak", success},
{"Jenik", "Jenik Poustevnicek", success},
}
deleteData := []testData{
{"Pepan", "Pepan Zdepan", success},
{"Honza", "Honza Novak", success},
{"Pepan", "Pepan Zdepan", failure},
{"Jenik", "Jenik Poustevnicek", success},
{"Honza", "Honza Novak", failure},
}
for _, v := range insertData {
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal {
t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
for _, v := range deleteData {
t.Logf("DELETE word=%v, success=%v", v.key, v.retVal)
if ok := trie.Delete([]byte(v.key)); ok != v.retVal {
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
}
func TestParticiaTrie_DeleteSubtree(t *testing.T) {
trie := NewTrie()
insertData := []testData{
{"P", 0, success},
{"Pe", 1, success},
{"Pep", 2, success},
{"Pepa", 3, success},
{"Pepa Zdepa", 4, success},
{"Pepa Kuchar", 5, success},
{"Honza", 6, success},
{"Jenik", 7, success},
}
deleteData := []testData{
{"Pe", -1, success},
{"Pe", -1, failure},
{"Honzik", -1, failure},
{"Honza", -1, success},
{"Honza", -1, failure},
{"Pep", -1, failure},
{"P", -1, success},
{"Nobody", -1, failure},
{"", -1, success},
}
for _, v := range insertData {
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
if ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal {
t.Fatalf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
for _, v := range deleteData {
t.Logf("DELETE_SUBTREE prefix=%v, success=%v", v.key, v.retVal)
if ok := trie.DeleteSubtree([]byte(v.key)); ok != v.retVal {
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
}
/*
func TestTrie_Dump(t *testing.T) {
trie := NewTrie()
data := []testData{
{"Honda", nil, success},
{"Honza", nil, success},
{"Jenik", nil, success},
{"Pepan", nil, success},
{"Pepin", nil, success},
}
for i, v := range data {
if _, ok := trie.Insert([]byte(v.key), v.value); ok != v.retVal {
t.Logf("INSERT %v %v", v.key, v.value)
t.Fatalf("Unexpected return value, expected=%v, got=%v", i, ok)
}
}
dump := `
+--+--+ Hon +--+--+ da
| |
| +--+ za
|
+--+ Jenik
|
+--+ Pep +--+--+ an
|
+--+ in
`
var buf bytes.Buffer
trie.Dump(buf)
if !bytes.Equal(buf.Bytes(), dump) {
t.Logf("DUMP")
t.Fatalf("Unexpected dump generated, expected\n\n%v\ngot\n\n%v", dump, buf.String())
}
}
*/
func TestTrie_compact(t *testing.T) {
trie := NewTrie()
trie.Insert(Prefix("a"), 0)
trie.Insert(Prefix("ab"), 0)
trie.Insert(Prefix("abc"), 0)
trie.Insert(Prefix("abcd"), 0)
trie.Insert(Prefix("abcde"), 0)
trie.Insert(Prefix("abcdef"), 0)
trie.Insert(Prefix("abcdefg"), 0)
trie.Insert(Prefix("abcdefgi"), 0)
trie.Insert(Prefix("abcdefgij"), 0)
trie.Insert(Prefix("abcdefgijk"), 0)
trie.Delete(Prefix("abcdef"))
trie.Delete(Prefix("abcde"))
trie.Delete(Prefix("abcdefg"))
trie.Delete(Prefix("a"))
trie.Delete(Prefix("abc"))
trie.Delete(Prefix("ab"))
trie.Visit(func(prefix Prefix, item Item) error {
// 97 ~~ 'a',
for ch := byte(97); ch <= 107; ch++ {
if c := bytes.Count(prefix, []byte{ch}); c > 1 {
t.Errorf("%q appeared in %q %v times", ch, prefix, c)
}
}
return nil
})
}
func TestTrie_longestCommonPrefixLenght(t *testing.T) {
trie := NewTrie()
trie.prefix = []byte("1234567890")
switch {
case trie.longestCommonPrefixLength([]byte("")) != 0:
t.Fail()
case trie.longestCommonPrefixLength([]byte("12345")) != 5:
t.Fail()
case trie.longestCommonPrefixLength([]byte("123789")) != 3:
t.Fail()
case trie.longestCommonPrefixLength([]byte("12345678901")) != 10:
t.Fail()
}
}
// Examples --------------------------------------------------------------------
func ExampleTrie() {
// Create a new tree.
trie := NewTrie()
// Insert some items.
trie.Insert(Prefix("Pepa Novak"), 1)
trie.Insert(Prefix("Pepa Sindelar"), 2)
trie.Insert(Prefix("Karel Macha"), 3)
trie.Insert(Prefix("Karel Hynek Macha"), 4)
// Just check if some things are present in the tree.
key := Prefix("Pepa Novak")
fmt.Printf("%q present? %v\n", key, trie.Match(key))
key = Prefix("Karel")
fmt.Printf("Anybody called %q here? %v\n", key, trie.MatchSubtree(key))
// Walk the tree.
trie.Visit(printItem)
// "Pepa Novak": 1
// "Pepa Sindelar": 2
// "Karel Macha": 3
// "Karel Hynek Macha": 4
// Walk a subtree.
trie.VisitSubtree(Prefix("Pepa"), printItem)
// "Pepa Novak": 1
// "Pepa Sindelar": 2
// Modify an item, then fetch it from the tree.
trie.Set(Prefix("Karel Hynek Macha"), 10)
key = Prefix("Karel Hynek Macha")
fmt.Printf("%q: %v\n", key, trie.Get(key))
// "Karel Hynek Macha": 10
// Walk prefixes.
prefix := Prefix("Karel Hynek Macha je kouzelnik")
trie.VisitPrefixes(prefix, printItem)
// "Karel Hynek Macha": 10
// Delete some items.
trie.Delete(Prefix("Pepa Novak"))
trie.Delete(Prefix("Karel Macha"))
// Walk again.
trie.Visit(printItem)
// "Pepa Sindelar": 2
// "Karel Hynek Macha": 10
// Delete a subtree.
trie.DeleteSubtree(Prefix("Pepa"))
// Print what is left.
trie.Visit(printItem)
// "Karel Hynek Macha": 10
// Output:
// "Pepa Novak" present? true
// Anybody called "Karel" here? true
// "Pepa Novak": 1
// "Pepa Sindelar": 2
// "Karel Macha": 3
// "Karel Hynek Macha": 4
// "Pepa Novak": 1
// "Pepa Sindelar": 2
// "Karel Hynek Macha": 10
// "Karel Hynek Macha": 10
// "Pepa Sindelar": 2
// "Karel Hynek Macha": 10
// "Karel Hynek Macha": 10
}
// Helpers ---------------------------------------------------------------------
func printItem(prefix Prefix, item Item) error {
fmt.Printf("%q: %v\n", prefix, item)
return nil
}

View File

@ -0,0 +1,78 @@
// Copyright (c) 2014 The go-patricia AUTHORS
//
// Use of this source code is governed by The MIT License
// that can be found in the LICENSE file.
package patricia
import (
"crypto/rand"
"reflect"
"testing"
)
// Tests -----------------------------------------------------------------------
func TestTrie_GetNonexistentPrefix(t *testing.T) {
trie := NewTrie()
data := []testData{
{"aba", 0, success},
}
for _, v := range data {
t.Logf("INSERT prefix=%v, item=%v, success=%v", v.key, v.value, v.retVal)
if ok := trie.Insert(Prefix(v.key), v.value); ok != v.retVal {
t.Errorf("Unexpected return value, expected=%v, got=%v", v.retVal, ok)
}
}
t.Logf("GET prefix=baa, expect item=nil")
if item := trie.Get(Prefix("baa")); item != nil {
t.Errorf("Unexpected return value, expected=<nil>, got=%v", item)
}
}
func TestTrie_RandomKitchenSink(t *testing.T) {
if testing.Short() {
t.Skip()
}
const count, size = 750000, 16
b := make([]byte, count+size+1)
if _, err := rand.Read(b); err != nil {
t.Fatal("error generating random bytes", err)
}
m := make(map[string]string)
for i := 0; i < count; i++ {
m[string(b[i:i+size])] = string(b[i+1 : i+size+1])
}
trie := NewTrie()
getAndDelete := func(k, v string) {
i := trie.Get(Prefix(k))
if i == nil {
t.Fatalf("item not found, prefix=%v", []byte(k))
} else if s, ok := i.(string); !ok {
t.Fatalf("unexpected item type, expecting=%v, got=%v", reflect.TypeOf(k), reflect.TypeOf(i))
} else if s != v {
t.Fatalf("unexpected item, expecting=%v, got=%v", []byte(k), []byte(s))
} else if !trie.Delete(Prefix(k)) {
t.Fatalf("delete failed, prefix=%v", []byte(k))
} else if i = trie.Get(Prefix(k)); i != nil {
t.Fatalf("unexpected item, expecting=<nil>, got=%v", i)
} else if trie.Delete(Prefix(k)) {
t.Fatalf("extra delete succeeded, prefix=%v", []byte(k))
}
}
for k, v := range m {
if !trie.Insert(Prefix(k), v) {
t.Fatalf("insert failed, prefix=%v", []byte(k))
}
if byte(k[size/2]) < 128 {
getAndDelete(k, v)
delete(m, k)
}
}
for k, v := range m {
getAndDelete(k, v)
}
}

View File

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"github.com/gorilla/mux"
"github.com/tchap/go-patricia/patricia"
"io/ioutil"
"net/http"
)
@ -134,11 +135,15 @@ func (bucket SynchronizedBucket) Get(context Context, path string) ([]byte, erro
path: path,
callback: callback,
}
switch response := <-callback; response.(type) {
case []byte:
return response.([]byte), nil
response := <-callback
switch response.(type) {
case error:
return nil, response.(error)
case nil:
return nil, errors.New("Object not found")
case interface{}:
return response.([]byte), nil
default:
return nil, errors.New("Unexpected error, service failed")
}
@ -167,16 +172,17 @@ func (bucket *SynchronizedBucket) closeChannel() {
}
func InMemoryStorageDriver(bucket string, input chan ObjectRequest) {
objects := make(map[string][]byte)
objects := patricia.NewTrie()
for request := range input {
prefix := patricia.Prefix(request.path)
fmt.Println("objects:", objects)
switch request.requestType {
case "GET":
fmt.Println("GET: " + request.path)
request.callback <- objects[request.path]
request.callback <- objects.Get(prefix)
case "PUT":
fmt.Println("PUT: " + request.path)
objects[request.path] = request.object
objects.Insert(prefix, request.object)
request.callback <- nil
default:
request.callback <- errors.New("Unexpected message")

View File

@ -117,7 +117,8 @@ func (s *MySuite) TestInMemoryBucketOperations(c *C) {
// get missing value
nilResult, err := bucket.Get(context, "foo")
c.Assert(nilResult, IsNil)
c.Assert(err, IsNil)
c.Assert(err, Not(IsNil))
c.Assert(err.Error(), Equals, "Object not found")
// add new value
err = bucket.Put(context, "foo", []byte("bar"))