Scott Baker | e7144bc | 2019-10-01 14:16:47 -0700 | [diff] [blame] | 1 | package diskv |
| 2 | |
| 3 | import ( |
| 4 | "sync" |
| 5 | |
| 6 | "github.com/google/btree" |
| 7 | ) |
| 8 | |
| 9 | // Index is a generic interface for things that can |
| 10 | // provide an ordered list of keys. |
| 11 | type Index interface { |
| 12 | Initialize(less LessFunction, keys <-chan string) |
| 13 | Insert(key string) |
| 14 | Delete(key string) |
| 15 | Keys(from string, n int) []string |
| 16 | } |
| 17 | |
| 18 | // LessFunction is used to initialize an Index of keys in a specific order. |
| 19 | type LessFunction func(string, string) bool |
| 20 | |
| 21 | // btreeString is a custom data type that satisfies the BTree Less interface, |
| 22 | // making the strings it wraps sortable by the BTree package. |
| 23 | type btreeString struct { |
| 24 | s string |
| 25 | l LessFunction |
| 26 | } |
| 27 | |
| 28 | // Less satisfies the BTree.Less interface using the btreeString's LessFunction. |
| 29 | func (s btreeString) Less(i btree.Item) bool { |
| 30 | return s.l(s.s, i.(btreeString).s) |
| 31 | } |
| 32 | |
| 33 | // BTreeIndex is an implementation of the Index interface using google/btree. |
| 34 | type BTreeIndex struct { |
| 35 | sync.RWMutex |
| 36 | LessFunction |
| 37 | *btree.BTree |
| 38 | } |
| 39 | |
| 40 | // Initialize populates the BTree tree with data from the keys channel, |
| 41 | // according to the passed less function. It's destructive to the BTreeIndex. |
| 42 | func (i *BTreeIndex) Initialize(less LessFunction, keys <-chan string) { |
| 43 | i.Lock() |
| 44 | defer i.Unlock() |
| 45 | i.LessFunction = less |
| 46 | i.BTree = rebuild(less, keys) |
| 47 | } |
| 48 | |
| 49 | // Insert inserts the given key (only) into the BTree tree. |
| 50 | func (i *BTreeIndex) Insert(key string) { |
| 51 | i.Lock() |
| 52 | defer i.Unlock() |
| 53 | if i.BTree == nil || i.LessFunction == nil { |
| 54 | panic("uninitialized index") |
| 55 | } |
| 56 | i.BTree.ReplaceOrInsert(btreeString{s: key, l: i.LessFunction}) |
| 57 | } |
| 58 | |
| 59 | // Delete removes the given key (only) from the BTree tree. |
| 60 | func (i *BTreeIndex) Delete(key string) { |
| 61 | i.Lock() |
| 62 | defer i.Unlock() |
| 63 | if i.BTree == nil || i.LessFunction == nil { |
| 64 | panic("uninitialized index") |
| 65 | } |
| 66 | i.BTree.Delete(btreeString{s: key, l: i.LessFunction}) |
| 67 | } |
| 68 | |
| 69 | // Keys yields a maximum of n keys in order. If the passed 'from' key is empty, |
| 70 | // Keys will return the first n keys. If the passed 'from' key is non-empty, the |
| 71 | // first key in the returned slice will be the key that immediately follows the |
| 72 | // passed key, in key order. |
| 73 | func (i *BTreeIndex) Keys(from string, n int) []string { |
| 74 | i.RLock() |
| 75 | defer i.RUnlock() |
| 76 | |
| 77 | if i.BTree == nil || i.LessFunction == nil { |
| 78 | panic("uninitialized index") |
| 79 | } |
| 80 | |
| 81 | if i.BTree.Len() <= 0 { |
| 82 | return []string{} |
| 83 | } |
| 84 | |
| 85 | btreeFrom := btreeString{s: from, l: i.LessFunction} |
| 86 | skipFirst := true |
| 87 | if len(from) <= 0 || !i.BTree.Has(btreeFrom) { |
| 88 | // no such key, so fabricate an always-smallest item |
| 89 | btreeFrom = btreeString{s: "", l: func(string, string) bool { return true }} |
| 90 | skipFirst = false |
| 91 | } |
| 92 | |
| 93 | keys := []string{} |
| 94 | iterator := func(i btree.Item) bool { |
| 95 | keys = append(keys, i.(btreeString).s) |
| 96 | return len(keys) < n |
| 97 | } |
| 98 | i.BTree.AscendGreaterOrEqual(btreeFrom, iterator) |
| 99 | |
| 100 | if skipFirst && len(keys) > 0 { |
| 101 | keys = keys[1:] |
| 102 | } |
| 103 | |
| 104 | return keys |
| 105 | } |
| 106 | |
| 107 | // rebuildIndex does the work of regenerating the index |
| 108 | // with the given keys. |
| 109 | func rebuild(less LessFunction, keys <-chan string) *btree.BTree { |
| 110 | tree := btree.New(2) |
| 111 | for key := range keys { |
| 112 | tree.ReplaceOrInsert(btreeString{s: key, l: less}) |
| 113 | } |
| 114 | return tree |
| 115 | } |