refactor TruncIndex to use a trie & vendor deps
Docker-DCO-1.1-Signed-off-by: Cristian Staretu <cristian.staretu@gmail.com> (github: unclejack)
This commit is contained in:
parent
e68d8598ca
commit
bf6177dbd2
2 changed files with 62 additions and 51 deletions
|
@ -1,31 +1,34 @@
|
||||||
package truncindex
|
package truncindex
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"index/suffixarray"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/tchap/go-patricia/patricia"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNoID = errors.New("prefix can't be empty")
|
||||||
)
|
)
|
||||||
|
|
||||||
// TruncIndex allows the retrieval of string identifiers by any of their unique prefixes.
|
// TruncIndex allows the retrieval of string identifiers by any of their unique prefixes.
|
||||||
// This is used to retrieve image and container IDs by more convenient shorthand prefixes.
|
// This is used to retrieve image and container IDs by more convenient shorthand prefixes.
|
||||||
type TruncIndex struct {
|
type TruncIndex struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
index *suffixarray.Index
|
trie *patricia.Trie
|
||||||
ids map[string]bool
|
ids map[string]struct{}
|
||||||
bytes []byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTruncIndex(ids []string) (idx *TruncIndex) {
|
func NewTruncIndex(ids []string) (idx *TruncIndex) {
|
||||||
idx = &TruncIndex{
|
idx = &TruncIndex{
|
||||||
ids: make(map[string]bool),
|
ids: make(map[string]struct{}),
|
||||||
bytes: []byte{' '},
|
trie: patricia.NewTrie(),
|
||||||
}
|
}
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
idx.ids[id] = true
|
idx.addId(id)
|
||||||
idx.bytes = append(idx.bytes, []byte(id+" ")...)
|
|
||||||
}
|
}
|
||||||
idx.index = suffixarray.New(idx.bytes)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,11 +36,16 @@ func (idx *TruncIndex) addId(id string) error {
|
||||||
if strings.Contains(id, " ") {
|
if strings.Contains(id, " ") {
|
||||||
return fmt.Errorf("Illegal character: ' '")
|
return fmt.Errorf("Illegal character: ' '")
|
||||||
}
|
}
|
||||||
if _, exists := idx.ids[id]; exists {
|
if id == "" {
|
||||||
return fmt.Errorf("Id already exists: %s", id)
|
return ErrNoID
|
||||||
|
}
|
||||||
|
if _, exists := idx.ids[id]; exists {
|
||||||
|
return fmt.Errorf("Id already exists: '%s'", id)
|
||||||
|
}
|
||||||
|
idx.ids[id] = struct{}{}
|
||||||
|
if inserted := idx.trie.Insert(patricia.Prefix(id), struct{}{}); !inserted {
|
||||||
|
return fmt.Errorf("Failed to insert id: %s", id)
|
||||||
}
|
}
|
||||||
idx.ids[id] = true
|
|
||||||
idx.bytes = append(idx.bytes, []byte(id+" ")...)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,56 +55,46 @@ func (idx *TruncIndex) Add(id string) error {
|
||||||
if err := idx.addId(id); err != nil {
|
if err := idx.addId(id); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
idx.index = suffixarray.New(idx.bytes)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (idx *TruncIndex) AddWithoutSuffixarrayUpdate(id string) error {
|
|
||||||
idx.Lock()
|
|
||||||
defer idx.Unlock()
|
|
||||||
return idx.addId(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (idx *TruncIndex) UpdateSuffixarray() {
|
|
||||||
idx.Lock()
|
|
||||||
defer idx.Unlock()
|
|
||||||
idx.index = suffixarray.New(idx.bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (idx *TruncIndex) Delete(id string) error {
|
func (idx *TruncIndex) Delete(id string) error {
|
||||||
idx.Lock()
|
idx.Lock()
|
||||||
defer idx.Unlock()
|
defer idx.Unlock()
|
||||||
if _, exists := idx.ids[id]; !exists {
|
if _, exists := idx.ids[id]; !exists || id == "" {
|
||||||
return fmt.Errorf("No such id: %s", id)
|
return fmt.Errorf("No such id: '%s'", id)
|
||||||
}
|
|
||||||
before, after, err := idx.lookup(id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
delete(idx.ids, id)
|
delete(idx.ids, id)
|
||||||
idx.bytes = append(idx.bytes[:before], idx.bytes[after:]...)
|
if deleted := idx.trie.Delete(patricia.Prefix(id)); !deleted {
|
||||||
idx.index = suffixarray.New(idx.bytes)
|
return fmt.Errorf("No such id: '%s'", id)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (idx *TruncIndex) lookup(s string) (int, int, error) {
|
|
||||||
offsets := idx.index.Lookup([]byte(" "+s), -1)
|
|
||||||
//log.Printf("lookup(%s): %v (index bytes: '%s')\n", s, offsets, idx.index.Bytes())
|
|
||||||
if offsets == nil || len(offsets) == 0 || len(offsets) > 1 {
|
|
||||||
return -1, -1, fmt.Errorf("No such id: %s", s)
|
|
||||||
}
|
}
|
||||||
offsetBefore := offsets[0] + 1
|
return nil
|
||||||
offsetAfter := offsetBefore + strings.Index(string(idx.bytes[offsetBefore:]), " ")
|
|
||||||
return offsetBefore, offsetAfter, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (idx *TruncIndex) Get(s string) (string, error) {
|
func (idx *TruncIndex) Get(s string) (string, error) {
|
||||||
idx.RLock()
|
idx.RLock()
|
||||||
defer idx.RUnlock()
|
defer idx.RUnlock()
|
||||||
before, after, err := idx.lookup(s)
|
var (
|
||||||
//log.Printf("Get(%s) bytes=|%s| before=|%d| after=|%d|\n", s, idx.bytes, before, after)
|
id string
|
||||||
if err != nil {
|
)
|
||||||
return "", err
|
if s == "" {
|
||||||
|
return "", ErrNoID
|
||||||
}
|
}
|
||||||
return string(idx.bytes[before:after]), err
|
subTreeVisitFunc := func(prefix patricia.Prefix, item patricia.Item) error {
|
||||||
|
if id != "" {
|
||||||
|
// we haven't found the ID if there are two or more IDs
|
||||||
|
id = ""
|
||||||
|
return fmt.Errorf("we've found two entries")
|
||||||
|
}
|
||||||
|
id = string(prefix)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := idx.trie.VisitSubtree(patricia.Prefix(s), subTreeVisitFunc); err != nil {
|
||||||
|
return "", fmt.Errorf("No such id: %s", s)
|
||||||
|
}
|
||||||
|
if id != "" {
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("No such id: %s", s)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,16 @@ func TestTruncIndex(t *testing.T) {
|
||||||
if err := index.Add(id); err != nil {
|
if err := index.Add(id); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add an empty id (should fail)
|
||||||
|
if err := index.Add(""); err == nil {
|
||||||
|
t.Fatalf("Adding an empty id should return an error")
|
||||||
|
}
|
||||||
|
|
||||||
// Get a non-existing id
|
// Get a non-existing id
|
||||||
assertIndexGet(t, index, "abracadabra", "", true)
|
assertIndexGet(t, index, "abracadabra", "", true)
|
||||||
|
// Get an empty id
|
||||||
|
assertIndexGet(t, index, "", "", true)
|
||||||
// Get the exact id
|
// Get the exact id
|
||||||
assertIndexGet(t, index, id, id, false)
|
assertIndexGet(t, index, id, id, false)
|
||||||
// The first letter should match
|
// The first letter should match
|
||||||
|
@ -60,6 +68,11 @@ func TestTruncIndex(t *testing.T) {
|
||||||
t.Fatalf("Deleting a non-existing id should return an error")
|
t.Fatalf("Deleting a non-existing id should return an error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deleting an empty id should return an error
|
||||||
|
if err := index.Delete(""); err == nil {
|
||||||
|
t.Fatal("Deleting an empty id should return an error")
|
||||||
|
}
|
||||||
|
|
||||||
// Deleting id2 should remove conflicts
|
// Deleting id2 should remove conflicts
|
||||||
if err := index.Delete(id2); err != nil {
|
if err := index.Delete(id2); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -84,7 +97,7 @@ func assertIndexGet(t *testing.T, index *TruncIndex, input, expectedResult strin
|
||||||
if result, err := index.Get(input); err != nil && !expectError {
|
if result, err := index.Get(input); err != nil && !expectError {
|
||||||
t.Fatalf("Unexpected error getting '%s': %s", input, err)
|
t.Fatalf("Unexpected error getting '%s': %s", input, err)
|
||||||
} else if err == nil && expectError {
|
} else if err == nil && expectError {
|
||||||
t.Fatalf("Getting '%s' should return an error", input)
|
t.Fatalf("Getting '%s' should return an error, not '%s'", input, result)
|
||||||
} else if result != expectedResult {
|
} else if result != expectedResult {
|
||||||
t.Fatalf("Getting '%s' returned '%s' instead of '%s'", input, result, expectedResult)
|
t.Fatalf("Getting '%s' returned '%s' instead of '%s'", input, result, expectedResult)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue