Skip to content

Commit 6f83916

Browse files
committed
basic ops
0 parents  commit 6f83916

File tree

6 files changed

+219
-0
lines changed

6 files changed

+219
-0
lines changed

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
## TBKV key-val store
2+
3+
You're looking at a simple zero-dependency in-memory key-value store for Golang.
4+
5+
All operations (Get/Set/Delete) are executed in a single, buffered loop, so the store provides some thread safety.
6+
7+
### Usage
8+
9+
10+
```Golang
11+
kvs := NewStore()
12+
13+
kvs.Set("key", "value")
14+
15+
val, err := kvs.Get("key")
16+
if err != nil {
17+
return err
18+
}
19+
20+
// val == "value"
21+
22+
kvs.Delete("key")
23+
24+
val, err := kvs.Get("key")
25+
if err != nil {
26+
// err == ErrNotFound
27+
return err
28+
}
29+
```

errors.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package tbkv
2+
3+
import "errors"
4+
5+
var ErrNotFound = errors.New("key not found")

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/tech-branch/tbkv
2+
3+
go 1.17

store.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package tbkv
2+
3+
type KVStore struct {
4+
data map[string]string
5+
save chan ItemRequest
6+
fetch chan Request
7+
delete chan Request
8+
}
9+
10+
func (s *KVStore) Get(key string) (string, error) {
11+
request := Request{
12+
Key: key,
13+
Result: make(chan Result, 1),
14+
}
15+
s.fetch <- request
16+
result := <-request.Result
17+
if !result.Ok {
18+
return "", ErrNotFound
19+
}
20+
return result.Value, nil
21+
}
22+
23+
func (s *KVStore) Set(key, value string) {
24+
setItemRequest := ItemRequest{
25+
Item: Item{
26+
Key: key,
27+
Value: value,
28+
},
29+
Ok: make(chan bool, 1),
30+
}
31+
s.save <- setItemRequest
32+
<-setItemRequest.Ok
33+
}
34+
35+
func (s *KVStore) Delete(key string) {
36+
request := Request{
37+
Key: key,
38+
Result: make(chan Result, 1),
39+
}
40+
s.delete <- request
41+
<-request.Result
42+
}
43+
44+
// NewStore creates a new KVStore.
45+
// requestBufferSize is the size of the request buffer.
46+
// If requestBufferSize is 0, it will use a default of 20.
47+
func NewStore(requestBufferSize uint8) KVStore {
48+
var buffer uint8
49+
if requestBufferSize == 0 {
50+
buffer = 20
51+
} else {
52+
buffer = requestBufferSize
53+
}
54+
kvs := KVStore{}
55+
kvs.data = make(map[string]string)
56+
kvs.save = make(chan ItemRequest, buffer)
57+
kvs.fetch = make(chan Request, buffer)
58+
kvs.delete = make(chan Request, buffer)
59+
go kvs.loop() // starts listening for requests
60+
return kvs
61+
}
62+
63+
// NewDefaultStore is a convenience function for creating a KVStore with sensible defaults.
64+
// It will create a KVStore with a request buffer size of 20.
65+
func NewDefaultStore() KVStore {
66+
return NewStore(0)
67+
}
68+
69+
func (s *KVStore) loop() error {
70+
for {
71+
select {
72+
case setRequest := <-s.save:
73+
s.data[setRequest.Item.Key] = setRequest.Item.Value
74+
setRequest.Ok <- true // cannot fail
75+
case getRequest := <-s.fetch:
76+
val, ok := s.data[getRequest.Key]
77+
getRequest.Result <- Result{
78+
Value: val,
79+
Ok: ok,
80+
}
81+
case delRequest := <-s.delete:
82+
delete(s.data, delRequest.Key)
83+
delRequest.Result <- Result{
84+
Value: delRequest.Key,
85+
Ok: true, // cannot fail
86+
}
87+
}
88+
}
89+
}

store_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package tbkv
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
)
7+
8+
func TestKVStore_Set(t *testing.T) {
9+
// testing the usual Set/Get flow
10+
kvs := NewDefaultStore()
11+
kvs.Set("key", "value")
12+
val, err := kvs.Get("key")
13+
if err != nil {
14+
t.Error(err)
15+
}
16+
if val != "value" {
17+
t.Errorf("Expected value to be 'value', got '%s'", val)
18+
}
19+
}
20+
21+
func TestKVStore_SetLargeBuffer(t *testing.T) {
22+
// testing the same Set/Get flow but with a bigger buffer
23+
kvs := NewStore(255)
24+
kvs.Set("key", "value")
25+
val, err := kvs.Get("key")
26+
if err != nil {
27+
t.Error(err)
28+
}
29+
if val != "value" {
30+
t.Errorf("Expected value to be 'value', got '%s'", val)
31+
}
32+
}
33+
34+
func TestKVStore_DoubleSet(t *testing.T) {
35+
// I want to overwrite entries without an issue
36+
kvs := NewDefaultStore()
37+
kvs.Set("key", "value1")
38+
kvs.Set("key", "value2")
39+
}
40+
41+
func TestKVStore_SetMultiple(t *testing.T) {
42+
// lets try with multiple consecutive sets
43+
kvs := NewDefaultStore()
44+
for i := 0; i < 20; i++ {
45+
kvs.Set(fmt.Sprint(i), fmt.Sprint(i*i))
46+
}
47+
val, err := kvs.Get("19")
48+
if err != nil {
49+
t.Error(err)
50+
}
51+
if val != "361" {
52+
t.Errorf("Expected value to be '361', got '%s'", val)
53+
}
54+
}
55+
56+
func TestKVStore_Delete(t *testing.T) {
57+
// I want to delete entries without an issue
58+
// I don't want to handle an error if entry does not exist, assume Ok
59+
kvs := NewDefaultStore()
60+
for i := 0; i < 20; i++ {
61+
kvs.Set(fmt.Sprint(i), fmt.Sprint(i*i))
62+
}
63+
kvs.Delete("19")
64+
// this is a no-op because the key doesn't exist
65+
kvs.Delete("19")
66+
67+
_, err := kvs.Get("19")
68+
if err != ErrNotFound {
69+
t.Errorf("Expected ErrNotFound, got '%s'", err)
70+
}
71+
72+
}

types.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package tbkv
2+
3+
type Item struct {
4+
Key string
5+
Value string
6+
}
7+
8+
type ItemRequest struct {
9+
Item Item
10+
Ok chan bool
11+
}
12+
13+
type Request struct {
14+
Key string
15+
Result chan Result
16+
}
17+
18+
type Result struct {
19+
Value string
20+
Ok bool
21+
}

0 commit comments

Comments
 (0)