summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitingore1
-rw-r--r--kv.go93
-rw-r--r--kv_test.go74
3 files changed, 168 insertions, 0 deletions
diff --git a/.gitingore b/.gitingore
new file mode 100644
index 0000000..0a7dfa0
--- /dev/null
+++ b/.gitingore
@@ -0,0 +1 @@
+testdb
diff --git a/kv.go b/kv.go
new file mode 100644
index 0000000..a2760d5
--- /dev/null
+++ b/kv.go
@@ -0,0 +1,93 @@
+package kv
+
+import (
+ "errors"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "sync"
+)
+
+var (
+ ErrInvalidKey = errors.New("kv: invalid key")
+ ErrNoSuchKey = errors.New("kv: no such key")
+)
+
+type DB struct {
+ cache map[string][]byte
+ basedir string
+ m sync.RWMutex
+}
+
+func NewDB(basedir string) (db *DB, err error) {
+ err = os.Mkdir(basedir, 0755)
+ if err != nil && !os.IsExist(err) {
+ return
+ }
+
+ return &DB{
+ cache: make(map[string][]byte),
+ basedir: basedir,
+ }, nil
+}
+
+func (db *DB) Set(key string, val []byte) (err error) {
+ if len(key) < 1 || strings.ContainsRune(key, os.PathSeparator) {
+ return ErrInvalidKey
+ }
+ db.m.Lock()
+ defer db.m.Unlock()
+
+ err = ioutil.WriteFile(filepath.Join(db.basedir, key), val, 0644)
+ if err != nil {
+ return
+ }
+
+ db.cache[key] = val
+ return
+}
+
+func (db *DB) Get(key string) (val []byte, err error) {
+ if len(key) < 1 || strings.ContainsRune(key, os.PathSeparator) {
+ return nil, ErrInvalidKey
+ }
+ db.m.Lock()
+ defer db.m.Unlock()
+
+ data, ok := db.cache[key]
+ if !ok {
+ data, err = ioutil.ReadFile(filepath.Join(db.basedir, key))
+ if err != nil {
+ if os.IsNotExist(err) {
+ err = ErrNoSuchKey
+ }
+ return nil, err
+ }
+
+ db.cache[key] = data
+ }
+
+ val = make([]byte, len(data))
+ copy(val, data)
+ return
+}
+
+func (db *DB) Remove(key string) (err error) {
+ if len(key) < 1 || strings.ContainsRune(key, os.PathSeparator) {
+ return ErrInvalidKey
+ }
+ db.m.Lock()
+ defer db.m.Unlock()
+
+ err = os.Remove(filepath.Join(db.basedir, key))
+ if err != nil {
+ if os.IsNotExist(err) {
+ err = ErrNoSuchKey
+ }
+ return
+ }
+
+ delete(db.cache, key)
+ return
+}
diff --git a/kv_test.go b/kv_test.go
new file mode 100644
index 0000000..0e22ad9
--- /dev/null
+++ b/kv_test.go
@@ -0,0 +1,74 @@
+package kv
+
+import (
+ "os"
+ "strings"
+ "testing"
+)
+
+type row struct {
+ key string
+ val string
+ valid bool
+}
+
+func testRow(key string, val string) row {
+ return row{key, val, !strings.ContainsRune(key, os.PathSeparator)}
+}
+
+func TestOne(t *testing.T) {
+ db, err := NewDatabse("testdb")
+ if err != nil {
+ t.Errorf("NewDatabase: %s", err)
+ }
+
+ table := []row{
+ testRow("k1", "v1"),
+ testRow("k2", "v2"),
+ testRow("k3/k3", "v3"),
+ testRow("!@#\\(&", "v4"),
+ testRow("k5\\\\k5", "v5"),
+ testRow("k6**6", "v6"),
+ testRow("k7\\**7", "v7"),
+ }
+
+ for _, r := range table {
+ err = db.Set(r.key, []byte(r.val))
+ if err != nil {
+ if r.valid {
+ t.Errorf("db.Set(%+v) expected success, got error: %s", r, err)
+ }
+ continue
+ }
+ if !r.valid {
+ t.Errorf("db.Set(%+v) expected error, got success", r)
+ continue
+ }
+
+ val, err := db.Get(r.key)
+ if err != nil {
+ t.Errorf("db.Get(%+v) expected success, got error: %s", r, err)
+ continue
+ }
+ if string(val) != r.val {
+ t.Errorf("db.Get(%+v) expected val %+v, got %+v", r, r.val, string(val))
+ }
+
+ err = db.Remove(r.key)
+ if err != nil {
+ t.Errorf("db.Remove(%+v) expected success, got error: %s", r, err)
+ continue
+ }
+ }
+
+ k := "non existstent key"
+ _, err = db.Get(k)
+ if err == nil || err != ErrNoSuchKey {
+ t.Errorf("db.Get(%s) expected error (%s), got (%s):", k, ErrNoSuchKey, err)
+ }
+
+ err = db.Remove(k)
+ if err == nil || err != ErrNoSuchKey {
+ t.Errorf("db.Remove(%s) expected error (%s), got (%s):", k, ErrNoSuchKey, err)
+ }
+}