diff options
-rw-r--r-- | .gitingore | 1 | ||||
-rw-r--r-- | kv.go | 93 | ||||
-rw-r--r-- | kv_test.go | 74 |
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 @@ -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) + } +} |