From 8a1cf7104cc3c2b147d98d11cee1604e57e3a4fd Mon Sep 17 00:00:00 2001 From: Bel LaPointe Date: Wed, 13 Mar 2019 14:06:46 -0600 Subject: [PATCH] Initial commit: bolt, map, leveldb support and test --- .gitignore | 4 ++++ bolt.go | 44 ++++++++++++++++++++++++++++++++++++++ db.go | 9 ++++++++ db_test.go | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ errors.go | 7 ++++++ leveldb.go | 47 +++++++++++++++++++++++++++++++++++++++++ map.go | 47 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 220 insertions(+) create mode 100644 .gitignore create mode 100644 bolt.go create mode 100644 db.go create mode 100644 db_test.go create mode 100644 errors.go create mode 100644 leveldb.go create mode 100644 map.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1cb77a7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.*.go +vendor +*.swp +*.swo diff --git a/bolt.go b/bolt.go new file mode 100644 index 0000000..f45b260 --- /dev/null +++ b/bolt.go @@ -0,0 +1,44 @@ +package storage + +import "github.com/boltdb/bolt" + +type Bolt struct { + db *bolt.DB +} + +func NewBolt(path string) (*Bolt, error) { + db, err := bolt.Open(path, 0600, nil) + return &Bolt{ + db: db, + }, err +} + +func (b *Bolt) Get(key string) ([]byte, error) { + var result []byte + err := b.db.View(func(tx *bolt.Tx) error { + bkt := tx.Bucket([]byte(DefaultNamespace)) + if bkt == nil { + return ErrNotFound + } + result = bkt.Get([]byte(key)) + if result == nil { + return ErrNotFound + } + return nil + }) + return result, err +} + +func (b *Bolt) Set(key string, value []byte) error { + return b.db.Update(func(tx *bolt.Tx) error { + bkt, err := tx.CreateBucketIfNotExists([]byte(DefaultNamespace)) + if err != nil { + return err + } + return bkt.Put([]byte(key), value) + }) +} + +func (b *Bolt) Close() error { + return b.db.Close() +} diff --git a/db.go b/db.go new file mode 100644 index 0000000..4d4aef3 --- /dev/null +++ b/db.go @@ -0,0 +1,9 @@ +package storage + +type DB interface { + Get(string) ([]byte, error) + Set(string, []byte) error + Close() error +} + +var DefaultNamespace = "namespace" diff --git a/db_test.go b/db_test.go new file mode 100644 index 0000000..a195b5e --- /dev/null +++ b/db_test.go @@ -0,0 +1,62 @@ +package storage + +import ( + "bytes" + "io/ioutil" + "os" + "path" + "testing" +) + +type mock struct { + m map[string][]byte +} + +func (mock *mock) Get(key string) ([]byte, error) { + v, ok := mock.m[key] + if ok { + return v, nil + } + return nil, ErrNotFound +} + +func (mock *mock) Set(key string, value []byte) error { + mock.m[key] = value + return nil +} + +func (mock *mock) Close() error { + return nil +} + +func TestImplementations(t *testing.T) { + dir, err := ioutil.TempDir("", "storage_tests_") + if err != nil { + t.Fatalf("cannot create temp dir: %v", err) + return + } + defer os.RemoveAll(dir) + + bolt, _ := NewBolt(path.Join(dir, "bolt")) + leveldb, _ := NewLevelDB(path.Join(dir, "leveldb")) + cases := []DB{ + &mock{m: make(map[string][]byte)}, + NewMap(), + bolt, + leveldb, + } + + validKey := "key" + validValue := []byte("value") + + for _, db := range cases { + if err := db.Set(validKey, validValue); err != nil { + t.Fatalf("cannot set %T: %v", db, err) + } + if v, err := db.Get(validKey); err != nil { + t.Fatalf("cannot get %T: %v", db, err) + } else if !bytes.Equal(v, validValue) { + t.Fatalf("wrong get %T: %q vs %q", db, v, validValue) + } + } +} diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..18157e9 --- /dev/null +++ b/errors.go @@ -0,0 +1,7 @@ +package storage + +import "errors" + +var ErrNotFound = errors.New("not found") + +var ErrNotImpl = errors.New("not implemented") diff --git a/leveldb.go b/leveldb.go new file mode 100644 index 0000000..7ece14d --- /dev/null +++ b/leveldb.go @@ -0,0 +1,47 @@ +package storage + +import ( + "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/filter" + "github.com/syndtr/goleveldb/leveldb/opt" +) + +type LevelDB struct { + db *leveldb.DB +} + +func NewLevelDB(path string) (*LevelDB, error) { + db, err := leveldb.OpenFile(path, &opt.Options{ + Filter: filter.NewBloomFilter(32), + }) + return &LevelDB{ + db: db, + }, err +} + +func (ldb *LevelDB) Get(key string) ([]byte, error) { + snapshot, err := ldb.db.GetSnapshot() + if err != nil { + return nil, err + } + defer snapshot.Release() + + v, err := snapshot.Get([]byte(key), nil) + if err == leveldb.ErrNotFound { + err = ErrNotFound + } else if err != nil { + return nil, err + } + + return v, err +} + +func (ldb *LevelDB) Set(key string, value []byte) error { + batch := &leveldb.Batch{} + batch.Put([]byte(key), value) + return ldb.db.Write(batch, nil) +} + +func (ldb *LevelDB) Close() error { + return ldb.db.Close() +} diff --git a/map.go b/map.go new file mode 100644 index 0000000..19defe8 --- /dev/null +++ b/map.go @@ -0,0 +1,47 @@ +package storage + +import ( + "fmt" +) + +type Map map[string][]byte + +func NewMap() Map { + m := make(map[string][]byte) + n := Map(m) + return n +} + +func (m Map) String() string { + s := "" + for k, v := range m { + if s != "" { + s += ",\n" + } + s += fmt.Sprintf("[%s]:[%s]", k, v) + } + return s +} + +func (m Map) Close() error { + m = nil + return nil +} + +func (m Map) Get(key string) ([]byte, error) { + if _, ok := m[key]; !ok { + return nil, ErrNotFound + } + return m[key], nil +} + +func (m Map) Set(key string, value []byte) error { + if value == nil { + if _, ok := m[key]; ok { + delete(m, key) + } + return nil + } + m[key] = value + return nil +}