Add span to hold sparse, ordered sets
This commit is contained in:
parent
eaae12101e
commit
1b75d9431b
|
@ -9,6 +9,11 @@ import (
|
|||
func TestQueue(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
lots := make([][]byte, 500)
|
||||
for i := range lots {
|
||||
lots[i] = []byte{1, 8, 7}
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
pushes [][]byte
|
||||
pops [][]byte
|
||||
|
@ -28,6 +33,8 @@ func TestQueue(t *testing.T) {
|
|||
[][]byte{{1}, {2}, {4}},
|
||||
[][]byte{{1}, {2}, {4}, nil, nil, nil},
|
||||
},
|
||||
// let's play with lots....
|
||||
{lots, append(lots, nil)},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
package state
|
||||
|
||||
import wire "github.com/tendermint/go-wire"
|
||||
|
||||
var (
|
||||
keys = []byte("keys")
|
||||
// uses dataKey from queue.go to prefix data
|
||||
)
|
||||
|
||||
// Span holds a number of different keys in a large range and allows
|
||||
// use to make some basic range queries, like highest between, lowest between...
|
||||
// All items are added with an index
|
||||
//
|
||||
// This becomes horribly inefficent as len(keys) => 1000+, but by then
|
||||
// hopefully we have access to the iavl tree to do this well
|
||||
//
|
||||
// TODO: doesn't handle deleting....
|
||||
type Span struct {
|
||||
store KVStore
|
||||
// keys is sorted ascending and cannot contain duplicates
|
||||
keys []uint64
|
||||
}
|
||||
|
||||
// NewSpan loads or initializes a span of keys
|
||||
func NewSpan(store KVStore) *Span {
|
||||
s := &Span{store: store}
|
||||
s.loadKeys()
|
||||
return s
|
||||
}
|
||||
|
||||
// Set puts a value at a given height
|
||||
func (s *Span) Set(h uint64, value []byte) {
|
||||
key := makeKey(h)
|
||||
s.store.Set(key, value)
|
||||
s.addKey(h)
|
||||
s.storeKeys()
|
||||
}
|
||||
|
||||
// Get returns the element at h if it exists
|
||||
func (s *Span) Get(h uint64) []byte {
|
||||
key := makeKey(h)
|
||||
return s.store.Get(key)
|
||||
}
|
||||
|
||||
// Bottom returns the lowest element in the Span, along with its index
|
||||
func (s *Span) Bottom() ([]byte, uint64) {
|
||||
if len(s.keys) == 0 {
|
||||
return nil, 0
|
||||
}
|
||||
h := s.keys[0]
|
||||
return s.Get(h), h
|
||||
}
|
||||
|
||||
// Top returns the highest element in the Span, along with its index
|
||||
func (s *Span) Top() ([]byte, uint64) {
|
||||
l := len(s.keys)
|
||||
if l == 0 {
|
||||
return nil, 0
|
||||
}
|
||||
h := s.keys[l-1]
|
||||
return s.Get(h), h
|
||||
}
|
||||
|
||||
// GTE returns the lowest element in the Span that is >= h, along with its index
|
||||
func (s *Span) GTE(h uint64) ([]byte, uint64) {
|
||||
for _, k := range s.keys {
|
||||
if k >= h {
|
||||
return s.Get(k), k
|
||||
}
|
||||
}
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// LTE returns the highest element in the Span that is <= h,
|
||||
// along with its index
|
||||
func (s *Span) LTE(h uint64) ([]byte, uint64) {
|
||||
var k uint64
|
||||
// start from the highest and go down for the first match
|
||||
for i := len(s.keys) - 1; i >= 0; i-- {
|
||||
k = s.keys[i]
|
||||
if k <= h {
|
||||
return s.Get(k), k
|
||||
}
|
||||
}
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// addKey inserts this key, maintaining sorted order, no duplicates
|
||||
func (s *Span) addKey(h uint64) {
|
||||
for i, k := range s.keys {
|
||||
// don't add duplicates
|
||||
if h == k {
|
||||
return
|
||||
}
|
||||
// insert before this key
|
||||
if h < k {
|
||||
// https://github.com/golang/go/wiki/SliceTricks
|
||||
s.keys = append(s.keys, 0)
|
||||
copy(s.keys[i+1:], s.keys[i:])
|
||||
s.keys[i] = h
|
||||
return
|
||||
}
|
||||
}
|
||||
// if it is higher than all (or empty keys), append
|
||||
s.keys = append(s.keys, h)
|
||||
}
|
||||
|
||||
func (s *Span) loadKeys() {
|
||||
b := s.store.Get(keys)
|
||||
if b == nil {
|
||||
return
|
||||
}
|
||||
err := wire.ReadBinaryBytes(b, &s.keys)
|
||||
// hahaha... just like i love to hate :)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Span) storeKeys() {
|
||||
b := wire.BinaryBytes(s.keys)
|
||||
s.store.Set(keys, b)
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type kv struct {
|
||||
k uint64
|
||||
v []byte
|
||||
}
|
||||
|
||||
type bscase struct {
|
||||
data []kv
|
||||
// these are the tests to try out
|
||||
top kv
|
||||
bottom kv
|
||||
gets []kv // for each item check the query matches
|
||||
lte []kv // value for lte queires...
|
||||
gte []kv // value for gte
|
||||
}
|
||||
|
||||
func TestBasicSpan(t *testing.T) {
|
||||
|
||||
a, b, c := []byte{0xaa}, []byte{0xbb}, []byte{0xcc}
|
||||
|
||||
lots := make([]kv, 1000)
|
||||
for i := range lots {
|
||||
lots[i] = kv{uint64(3 * i), []byte{byte(i / 100), byte(i % 100)}}
|
||||
}
|
||||
|
||||
cases := []bscase{
|
||||
// simplest queries
|
||||
{
|
||||
[]kv{{1, a}, {3, b}, {5, c}},
|
||||
kv{5, c},
|
||||
kv{1, a},
|
||||
[]kv{{1, a}, {3, b}, {5, c}},
|
||||
[]kv{{2, a}, {77, c}, {3, b}, {0, nil}}, // lte
|
||||
[]kv{{6, nil}, {2, b}, {1, a}}, // gte
|
||||
},
|
||||
// add out of order
|
||||
{
|
||||
[]kv{{7, a}, {2, b}, {6, c}},
|
||||
kv{7, a},
|
||||
kv{2, b},
|
||||
[]kv{{2, b}, {6, c}, {7, a}},
|
||||
[]kv{{4, b}, {7, a}, {1, nil}}, // lte
|
||||
[]kv{{4, c}, {7, a}, {1, b}}, // gte
|
||||
},
|
||||
// add out of order and with duplicates
|
||||
{
|
||||
[]kv{{7, a}, {2, b}, {6, c}, {7, c}, {6, b}, {2, a}},
|
||||
kv{7, c},
|
||||
kv{2, a},
|
||||
[]kv{{2, a}, {6, b}, {7, c}},
|
||||
[]kv{{5, a}, {6, b}, {123, c}}, // lte
|
||||
[]kv{{0, a}, {3, b}, {7, c}, {8, nil}}, // gte
|
||||
},
|
||||
// try lots...
|
||||
{
|
||||
lots,
|
||||
lots[len(lots)-1],
|
||||
lots[0],
|
||||
lots,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
store := NewMemKVStore()
|
||||
|
||||
// initialize a queue and add items
|
||||
s := NewSpan(store)
|
||||
for _, x := range tc.data {
|
||||
s.Set(x.k, x.v)
|
||||
}
|
||||
|
||||
testSpan(t, i, s, tc)
|
||||
// reload and try the queries again
|
||||
s2 := NewSpan(store)
|
||||
testSpan(t, i+10, s2, tc)
|
||||
}
|
||||
}
|
||||
|
||||
func testSpan(t *testing.T, idx int, s *Span, tc bscase) {
|
||||
assert := assert.New(t)
|
||||
i := strconv.Itoa(idx)
|
||||
|
||||
v, k := s.Top()
|
||||
assert.Equal(tc.top.k, k, i)
|
||||
assert.Equal(tc.top.v, v, i)
|
||||
|
||||
v, k = s.Bottom()
|
||||
assert.Equal(tc.bottom.k, k, i)
|
||||
assert.Equal(tc.bottom.v, v, i)
|
||||
|
||||
for _, g := range tc.gets {
|
||||
v = s.Get(g.k)
|
||||
assert.Equal(g.v, v, i)
|
||||
}
|
||||
|
||||
for _, l := range tc.lte {
|
||||
v, k = s.LTE(l.k)
|
||||
assert.Equal(l.v, v, i)
|
||||
if l.v != nil {
|
||||
assert.True(k <= l.k, i)
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range tc.gte {
|
||||
v, k = s.GTE(t.k)
|
||||
assert.Equal(t.v, v, i)
|
||||
if t.v != nil {
|
||||
assert.True(k >= t.k, i)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue