mirror of
https://github.com/jimeh/ozu.io.git
synced 2026-02-19 08:06:39 +00:00
Initial work on storage package
This commit is contained in:
81
storage/goleveldb_store.go
Normal file
81
storage/goleveldb_store.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
)
|
||||
|
||||
// GoleveldbStore allows storing data into a goleveldb database.
|
||||
type GoleveldbStore struct {
|
||||
DB *leveldb.DB
|
||||
}
|
||||
|
||||
// NewGoleveldbStore creates a new GoleveldbStore using given path to persist
|
||||
// data.
|
||||
func NewGoleveldbStore(path string) (GoleveldbStore, error) {
|
||||
db, err := leveldb.OpenFile(path, nil)
|
||||
if err != nil {
|
||||
return GoleveldbStore{}, err
|
||||
}
|
||||
|
||||
return GoleveldbStore{DB: db}, nil
|
||||
}
|
||||
|
||||
// Close underlying goleveldb database.
|
||||
func (s *GoleveldbStore) Close() error {
|
||||
return s.DB.Close()
|
||||
}
|
||||
|
||||
// Get a given key's value.
|
||||
func (s *GoleveldbStore) Get(key []byte) ([]byte, error) {
|
||||
value, err := s.DB.Get(key, nil)
|
||||
if err != nil && err.Error() == "leveldb: not found" {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
return value, err
|
||||
}
|
||||
|
||||
// Set a given key's to the specified value.
|
||||
func (s *GoleveldbStore) Set(key []byte, value []byte) error {
|
||||
return s.DB.Put(key, value, nil)
|
||||
}
|
||||
|
||||
// Delete a given key.
|
||||
func (s *GoleveldbStore) Delete(key []byte) error {
|
||||
return s.DB.Delete(key, nil)
|
||||
}
|
||||
|
||||
// Incr increments a given key (must be numeric-like value)
|
||||
func (s *GoleveldbStore) Incr(key []byte) (int, error) {
|
||||
tx, err := s.DB.OpenTransaction()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
value, err := tx.Get(key, nil)
|
||||
if value == nil {
|
||||
value = []byte("0")
|
||||
}
|
||||
|
||||
num, err := strconv.Atoi(string(value))
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
num++
|
||||
value = []byte(strconv.Itoa(num))
|
||||
|
||||
err = tx.Put(key, value, nil)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return num, nil
|
||||
}
|
||||
167
storage/goleveldb_store_test.go
Normal file
167
storage/goleveldb_store_test.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
)
|
||||
|
||||
// Setup Suite
|
||||
|
||||
var testDbPath = "./goleveldb_test_data"
|
||||
|
||||
var examples = []struct {
|
||||
key []byte
|
||||
value []byte
|
||||
}{
|
||||
{key: []byte("hello"), value: []byte("world")},
|
||||
{key: []byte("foo"), value: []byte("bar")},
|
||||
{key: []byte("wtf"), value: []byte("dude")},
|
||||
}
|
||||
|
||||
type GoleveldbStoreSuite struct {
|
||||
suite.Suite
|
||||
store Store
|
||||
db *leveldb.DB
|
||||
}
|
||||
|
||||
func (s *GoleveldbStoreSuite) Seed() {
|
||||
for _, e := range examples {
|
||||
err := s.db.Put(e.key, e.value, nil)
|
||||
s.Require().Nil(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *GoleveldbStoreSuite) SetupTest() {
|
||||
store, err := NewGoleveldbStore(testDbPath)
|
||||
s.Nil(err)
|
||||
s.store = &store
|
||||
s.db = store.DB
|
||||
}
|
||||
|
||||
func (s *GoleveldbStoreSuite) TearDownTest() {
|
||||
s.store.Close()
|
||||
os.RemoveAll(testDbPath)
|
||||
}
|
||||
|
||||
// Tests
|
||||
|
||||
func (s *GoleveldbStoreSuite) TestSet() {
|
||||
for _, e := range examples {
|
||||
err := s.store.Set(e.key, e.value)
|
||||
s.Nil(err)
|
||||
}
|
||||
|
||||
for _, e := range examples {
|
||||
result, _ := s.db.Get(e.key, nil)
|
||||
s.Equal(e.value, result)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *GoleveldbStoreSuite) TestGetExisting() {
|
||||
s.Seed()
|
||||
|
||||
for _, e := range examples {
|
||||
result, err := s.store.Get(e.key)
|
||||
s.Nil(err)
|
||||
s.Equal(e.value, result)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *GoleveldbStoreSuite) TestGetNonExistant() {
|
||||
result, err := s.store.Get([]byte("does-not-exist"))
|
||||
s.Nil(err)
|
||||
s.Nil(result)
|
||||
}
|
||||
|
||||
func (s *GoleveldbStoreSuite) TestDeleteExisting() {
|
||||
s.Seed()
|
||||
|
||||
for _, e := range examples {
|
||||
value, _ := s.db.Get(e.key, nil)
|
||||
s.Require().Equal(e.value, value)
|
||||
|
||||
err := s.store.Delete(e.key)
|
||||
s.Nil(err)
|
||||
|
||||
has, _ := s.db.Has(e.key, nil)
|
||||
s.Equal(false, has)
|
||||
result, _ := s.db.Get(e.key, nil)
|
||||
s.Equal([]byte{}, result)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *GoleveldbStoreSuite) TestDeleteNonExistant() {
|
||||
err := s.store.Delete([]byte("does-not-exist"))
|
||||
s.Nil(err)
|
||||
}
|
||||
|
||||
func (s *GoleveldbStoreSuite) TestIncrExisting() {
|
||||
key := []byte("my-counter")
|
||||
|
||||
err := s.db.Put(key, []byte("5"), nil)
|
||||
s.Require().Nil(err)
|
||||
|
||||
result, err := s.store.Incr(key)
|
||||
s.Nil(err)
|
||||
s.Equal(6, result)
|
||||
}
|
||||
|
||||
func (s *GoleveldbStoreSuite) TestIncrNonExistant() {
|
||||
for i := 1; i < 10; i++ {
|
||||
result, err := s.store.Incr([]byte("counter"))
|
||||
|
||||
s.Nil(err)
|
||||
s.Equal(i, result)
|
||||
}
|
||||
}
|
||||
|
||||
// Run Suite
|
||||
|
||||
func TestGoleveldbStoreSuite(t *testing.T) {
|
||||
suite.Run(t, new(GoleveldbStoreSuite))
|
||||
}
|
||||
|
||||
// Benchmarks
|
||||
|
||||
func BenchmarkGet(b *testing.B) {
|
||||
key := []byte("hello")
|
||||
value := []byte("world")
|
||||
store, _ := NewGoleveldbStore(testDbPath)
|
||||
_ = store.Set(key, value)
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = store.Get(key)
|
||||
}
|
||||
|
||||
_ = store.Close()
|
||||
_ = os.RemoveAll(testDbPath)
|
||||
}
|
||||
|
||||
func BenchmarkSet(b *testing.B) {
|
||||
key := []byte("hello")
|
||||
value := []byte("world")
|
||||
store, _ := NewGoleveldbStore(testDbPath)
|
||||
_ = store.Set(key, value)
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
_ = store.Set(append(key, string(n)...), append(value, string(n)...))
|
||||
}
|
||||
|
||||
_ = store.Close()
|
||||
_ = os.RemoveAll(testDbPath)
|
||||
}
|
||||
|
||||
func BenchmarkIncr(b *testing.B) {
|
||||
key := []byte("incr-benchmark-counter")
|
||||
store, _ := NewGoleveldbStore(testDbPath)
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = store.Incr(key)
|
||||
}
|
||||
|
||||
_ = store.Close()
|
||||
_ = os.RemoveAll(testDbPath)
|
||||
}
|
||||
10
storage/store.go
Normal file
10
storage/store.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package storage
|
||||
|
||||
// Storage defines a standard interface for storage.
|
||||
type Store interface {
|
||||
Close() error
|
||||
Get([]byte) ([]byte, error)
|
||||
Set([]byte, []byte) error
|
||||
Delete([]byte) error
|
||||
Incr([]byte) (int, error)
|
||||
}
|
||||
Reference in New Issue
Block a user