diff --git a/store/bolt.go b/store/bolt.go new file mode 100644 index 0000000..700469c --- /dev/null +++ b/store/bolt.go @@ -0,0 +1,58 @@ +package store + +import ( + "github.com/boltdb/bolt" +) + +type BoltClient struct { + db *bolt.DB +} + +func NewBolt(path string) (*BoltClient, error) { + db, err := bolt.Open(path, 0600, nil) + if err != nil { + return nil, err + } + return &BoltClient{ + db: db, + }, nil +} + +func (bc *BoltClient) Set(namespace, key string, value []byte) error { + return bc.db.Update(func(tx *bolt.Tx) error { + bucket, err := tx.CreateBucketIfNotExists([]byte(namespace)) + if err != nil { + return err + } + return bucket.Put([]byte(key), value) + }) +} + +func (bc *BoltClient) List(namespace, key string) ([]string, error) { + found := []string{} + err := bc.db.View(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(namespace)) + if bucket == nil { + return nil + } + c := bucket.Cursor() + for k, _ := c.Seek([]byte(key)); k != nil; k, _ = c.Next() { + found = append(found, string(k)) + } + return nil + }) + return found, err +} + +func (bc *BoltClient) Get(namespace, key string) ([]byte, error) { + var result []byte + err := bc.db.View(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(namespace)) + if bucket == nil { + return nil + } + result = bucket.Get([]byte(key)) + return nil + }) + return result, err +} diff --git a/store/bolt_test.go b/store/bolt_test.go new file mode 100644 index 0000000..c6f9011 --- /dev/null +++ b/store/bolt_test.go @@ -0,0 +1,72 @@ +package store + +import ( + "io/ioutil" + "os" + "testing" +) + +func Test_BoltSetListGet(t *testing.T) { + cases := []struct { + ns string + keys []string + values []string + search string + results int + }{ + { + ns: "namespace", + keys: []string{}, + values: []string{}, + search: "", + results: 0, + }, + { + ns: "namespace", + keys: []string{"key1", "key2", "aey3"}, + values: []string{"v1", "v2", "a3"}, + search: "k", + results: 2, + }, + { + ns: "namespace", + keys: []string{"key1", "key2", "key3"}, + values: []string{"v1", "v2", "v3"}, + search: "k", + results: 3, + }, + } + + for _, c := range cases { + tmp, err := ioutil.TempFile(".", "any*") + if err != nil { + t.Fatalf("could not make tempfile: %v", err) + } + defer os.Remove(tmp.Name()) + var sc Client + sc, err = NewBolt(tmp.Name()) + if err != nil { + t.Errorf("failed to create bolt %v: %v", sc, err) + } + + for i := range c.keys { + if err := sc.Set(c.ns, c.keys[i], []byte(c.values[i])); err != nil { + t.Errorf("failed to set bolt: %v", err) + } + } + + if results, err := sc.List(c.ns, c.search); err != nil { + t.Errorf("failed to list bolt: %v", err) + } else if len(results) != c.results { + t.Errorf("failed to list bolt: missing results for %q: %v, expceted %v", c.ns, len(results), len(c.keys)) + } + + if len(c.keys) > 0 { + if result, err := sc.Get(c.ns, c.keys[0]); err != nil { + t.Errorf("failed to get bolt: %v", err) + } else if string(result) != c.values[0] { + t.Errorf("failed to get bolt: got wrong result: wanted %v, got %v", c.values[0], string(result)) + } + } + } +} diff --git a/store/store.go b/store/store.go new file mode 100644 index 0000000..6d9cc83 --- /dev/null +++ b/store/store.go @@ -0,0 +1,7 @@ +package store + +type Client interface { + Get(string, string) ([]byte, error) + Set(string, string, []byte) error + List(string, string) ([]string, error) +}