From d1a330187e13d3c644c77a27c27ad13ea229ed72 Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Tue, 5 Jul 2016 23:16:33 +0100 Subject: [PATCH] Restructure storage code a bit --- storage/goleveldb_store.go | 81 ------------- storage/goleveldbstore/store.go | 98 +++++++++++++++ .../store_test.go} | 112 ++++++++++++------ storage/store.go | 2 +- 4 files changed, 173 insertions(+), 120 deletions(-) delete mode 100644 storage/goleveldb_store.go create mode 100644 storage/goleveldbstore/store.go rename storage/{goleveldb_store_test.go => goleveldbstore/store_test.go} (55%) diff --git a/storage/goleveldb_store.go b/storage/goleveldb_store.go deleted file mode 100644 index 4176488..0000000 --- a/storage/goleveldb_store.go +++ /dev/null @@ -1,81 +0,0 @@ -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 -} diff --git a/storage/goleveldbstore/store.go b/storage/goleveldbstore/store.go new file mode 100644 index 0000000..7725b1b --- /dev/null +++ b/storage/goleveldbstore/store.go @@ -0,0 +1,98 @@ +package goleveldbstore + +import ( + "errors" + "strconv" + + "github.com/syndtr/goleveldb/leveldb" +) + +// DefaultSequenceKey is used by NextSequence(). +var DefaultSequenceKey = []byte("__SEQUENCE_ID__") + +// ErrNotFound is returned when Get() tries to fetch a non-existent key. +var ErrNotFound = errors.New("not found") + +// New creates a new Store using given path to persist data. +func New(path string) (*Store, error) { + db, err := leveldb.OpenFile(path, nil) + if err != nil { + return &Store{}, err + } + + store := Store{ + DB: db, + SequenceKey: DefaultSequenceKey, + } + + return &store, nil +} + +// Store allows storing data into a goleveldb database. +type Store struct { + DB *leveldb.DB + SequenceKey []byte +} + +// Close underlying goleveldb database. +func (s *Store) Close() error { + return s.DB.Close() +} + +// Get a given key's value. +func (s *Store) Get(key []byte) ([]byte, error) { + value, err := s.DB.Get(key, nil) + if err != nil && err.Error() == "leveldb: not found" { + return nil, ErrNotFound + } + + return value, err +} + +// Set a given key's to the specified value. +func (s *Store) Set(key []byte, value []byte) error { + return s.DB.Put(key, value, nil) +} + +// Delete a given key. +func (s *Store) Delete(key []byte) error { + return s.DB.Delete(key, nil) +} + +// NextSequence returns a auto-incrementing int. +func (s *Store) NextSequence() (int, error) { + return s.Incr(s.SequenceKey) +} + +// Incr increments a given key (must be numeric-like value) +func (s *Store) 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 +} diff --git a/storage/goleveldb_store_test.go b/storage/goleveldbstore/store_test.go similarity index 55% rename from storage/goleveldb_store_test.go rename to storage/goleveldbstore/store_test.go index ad50891..00f35f1 100644 --- a/storage/goleveldb_store_test.go +++ b/storage/goleveldbstore/store_test.go @@ -1,9 +1,10 @@ -package storage +package goleveldbstore import ( "os" "testing" + "github.com/jimeh/ozu.io/storage" "github.com/stretchr/testify/suite" "github.com/syndtr/goleveldb/leveldb" ) @@ -21,37 +22,41 @@ var examples = []struct { {key: []byte("wtf"), value: []byte("dude")}, } -type GoleveldbStoreSuite struct { +type StoreSuite struct { suite.Suite - store Store + store *Store db *leveldb.DB } -func (s *GoleveldbStoreSuite) Seed() { +func (s *StoreSuite) Seed() { for _, e := range examples { err := s.db.Put(e.key, e.value, nil) - s.Require().Nil(err) + s.Require().NoError(err) } } -func (s *GoleveldbStoreSuite) SetupTest() { - store, err := NewGoleveldbStore(testDbPath) - s.Nil(err) - s.store = &store +func (s *StoreSuite) SetupTest() { + store, err := New(testDbPath) + s.Require().NoError(err) + s.store = store s.db = store.DB } -func (s *GoleveldbStoreSuite) TearDownTest() { - s.store.Close() - os.RemoveAll(testDbPath) +func (s *StoreSuite) TearDownTest() { + _ = s.store.Close() + _ = os.RemoveAll(testDbPath) } // Tests -func (s *GoleveldbStoreSuite) TestSet() { +func (s *StoreSuite) TestStoreInterface() { + s.Implements(new(storage.Store), new(Store)) +} + +func (s *StoreSuite) TestSet() { for _, e := range examples { err := s.store.Set(e.key, e.value) - s.Nil(err) + s.NoError(err) } for _, e := range examples { @@ -60,23 +65,23 @@ func (s *GoleveldbStoreSuite) TestSet() { } } -func (s *GoleveldbStoreSuite) TestGetExisting() { +func (s *StoreSuite) TestGetExisting() { s.Seed() for _, e := range examples { result, err := s.store.Get(e.key) - s.Nil(err) + s.NoError(err) s.Equal(e.value, result) } } -func (s *GoleveldbStoreSuite) TestGetNonExistant() { +func (s *StoreSuite) TestGetNonExistant() { result, err := s.store.Get([]byte("does-not-exist")) - s.Nil(err) s.Nil(result) + s.EqualError(err, "not found") } -func (s *GoleveldbStoreSuite) TestDeleteExisting() { +func (s *StoreSuite) TestDeleteExisting() { s.Seed() for _, e := range examples { @@ -84,7 +89,7 @@ func (s *GoleveldbStoreSuite) TestDeleteExisting() { s.Require().Equal(e.value, value) err := s.store.Delete(e.key) - s.Nil(err) + s.NoError(err) has, _ := s.db.Has(e.key, nil) s.Equal(false, has) @@ -93,43 +98,62 @@ func (s *GoleveldbStoreSuite) TestDeleteExisting() { } } -func (s *GoleveldbStoreSuite) TestDeleteNonExistant() { +func (s *StoreSuite) TestDeleteNonExistant() { err := s.store.Delete([]byte("does-not-exist")) - s.Nil(err) + s.NoError(err) } -func (s *GoleveldbStoreSuite) TestIncrExisting() { - key := []byte("my-counter") +func (s *StoreSuite) TestNextSequenceExisting() { + err := s.db.Put(DefaultSequenceKey, []byte("5"), nil) + s.Require().NoError(err) - err := s.db.Put(key, []byte("5"), nil) - s.Require().Nil(err) - - result, err := s.store.Incr(key) - s.Nil(err) + result, err := s.store.NextSequence() + s.NoError(err) s.Equal(6, result) } -func (s *GoleveldbStoreSuite) TestIncrNonExistant() { +func (s *StoreSuite) TestNextSequenceNonExistant() { + for i := 1; i < 10; i++ { + result, err := s.store.NextSequence() + + s.NoError(err) + s.Equal(i, result) + } +} + +func (s *StoreSuite) TestIncrExisting() { + key := []byte("my-counter") + + err := s.db.Put(key, []byte("5"), nil) + s.Require().NoError(err) + + result, err := s.store.Incr(key) + s.NoError(err) + s.Equal(6, result) +} + +func (s *StoreSuite) TestIncrNonExistant() { for i := 1; i < 10; i++ { result, err := s.store.Incr([]byte("counter")) - s.Nil(err) + s.NoError(err) s.Equal(i, result) } } // Run Suite -func TestGoleveldbStoreSuite(t *testing.T) { - suite.Run(t, new(GoleveldbStoreSuite)) +func TestStoreSuite(t *testing.T) { + suite.Run(t, new(StoreSuite)) } // Benchmarks func BenchmarkGet(b *testing.B) { + store, _ := New(testDbPath) + key := []byte("hello") value := []byte("world") - store, _ := NewGoleveldbStore(testDbPath) _ = store.Set(key, value) for n := 0; n < b.N; n++ { @@ -141,13 +165,25 @@ func BenchmarkGet(b *testing.B) { } func BenchmarkSet(b *testing.B) { + store, _ := New(testDbPath) + 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.Set(append(key, string(n)...), value) + } + + _ = store.Close() + _ = os.RemoveAll(testDbPath) +} + +func BenchmarkNextSequence(b *testing.B) { + store, _ := New(testDbPath) + + for n := 0; n < b.N; n++ { + _, _ = store.NextSequence() } _ = store.Close() @@ -155,9 +191,9 @@ func BenchmarkSet(b *testing.B) { } func BenchmarkIncr(b *testing.B) { - key := []byte("incr-benchmark-counter") - store, _ := NewGoleveldbStore(testDbPath) + store, _ := New(testDbPath) + key := []byte("incr-benchmark-counter") for n := 0; n < b.N; n++ { _, _ = store.Incr(key) } diff --git a/storage/store.go b/storage/store.go index 7cf1270..6745cf6 100644 --- a/storage/store.go +++ b/storage/store.go @@ -6,5 +6,5 @@ type Store interface { Get([]byte) ([]byte, error) Set([]byte, []byte) error Delete([]byte) error - Incr([]byte) (int, error) + NextSequence() (int, error) }