store simplification (use name,key->value so we can manage many k-v stores)
authornsz <nsz@port70.net>
Wed, 16 Nov 2011 11:36:33 +0000 (12:36 +0100)
committernsz <nsz@port70.net>
Wed, 16 Nov 2011 11:36:33 +0000 (12:36 +0100)
store/Makefile [new file with mode: 0644]
store/store.go
store/store_test.go [new file with mode: 0644]

diff --git a/store/Makefile b/store/Makefile
new file mode 100644 (file)
index 0000000..23c7767
--- /dev/null
@@ -0,0 +1,6 @@
+include $(GOROOT)/src/Make.inc
+
+TARG=epoint/store
+GOFILES=store.go
+
+include $(GOROOT)/src/Make.pkg
index 48614f2..2595f88 100644 (file)
@@ -1,29 +1,26 @@
 package store
 
 // persistent key-value store
-// TODO: thread safety, persistence, efficient update and query, incremental backup
+// multiple key-value store can be managed by a single db connection
+// each store has a name, before usage the name of the store must be
+// ensured to exist
+//
+// TODO: this is a toy implementation
 
 import (
-       "fmt"
        "os"
        "path/filepath"
        "io/ioutil"
-       "sync"
 )
 
 
 type Conn struct {
-       name string
        path string
-       mutex sync.RWMutex
-       memstore map[string][]byte
 }
 
-func Open(name string) (c *Conn, err error) {
+func Open(root string) (c *Conn, err error) {
        c = new(Conn)
-       c.memstore = make(map[string][]byte)
-       c.name = name
-       c.path, err = filepath.Abs(name)
+       c.path, err = filepath.Abs(root)
        if err != nil {
                return
        }
@@ -31,49 +28,30 @@ func Open(name string) (c *Conn, err error) {
        if err != nil {
                return
        }
-       err = filepath.Walk(c.path, func(path string, info *os.FileInfo, err error) error {
-               if info.IsDirectory() {
-                       if info.Name == c.name {
-                               return nil
-                       }
-                       return filepath.SkipDir
-               }
-               if err != nil {
-                       return err
-               }
-               k := info.Name
-               v, err := ioutil.ReadFile(filepath.Join(c.path, k))
-               if err != nil {
-                       return err
-               }
-               c.memstore[k] = v
-               return nil
-       })
        return
 }
 
-func (c *Conn) Get(k string) (v []byte, err error) {
-       c.mutex.RLock()
-       defer c.mutex.RUnlock()
-       v, ok := c.memstore[k]
-       if !ok {
-               err = fmt.Errorf("key not found")
-       }
-       return
+func (c *Conn) Get(name, k string) (v []byte, err error) {
+       return ioutil.ReadFile(filepath.Join(c.path, name, k))
+}
+
+func (c *Conn) Ensure(name string) (err error) {
+       return os.MkdirAll(filepath.Join(c.path, name), 0755)
 }
 
-func (c *Conn) Set(k string, v []byte) (err error) {
-       c.mutex.Lock()
-       defer c.mutex.Unlock()
-       f, err := os.Create(filepath.Join(c.path, k))
+func (c *Conn) Set(name, k string, v []byte) (err error) {
+       fn := filepath.Join(c.path, name, k)
+       // os.O_SYNC
+       f, err := os.Create(fn+".tmp")
        if err != nil {
                return
        }
+       defer f.Close()
        _, err = f.Write(v)
        if err != nil {
                return
        }
-       c.memstore[k] = v
+       err = os.Rename(fn+".tmp", fn)
        return
 }
 
diff --git a/store/store_test.go b/store/store_test.go
new file mode 100644 (file)
index 0000000..f36fcbb
--- /dev/null
@@ -0,0 +1,79 @@
+package store
+
+import (
+       "testing"
+)
+
+var testData = map[string]string {
+       "A" : "a",
+       "B" : "b",
+       "C" : "c",
+       "D" : "d",
+       "foo_bar-baz" : "a\nb\nc\nd\n",
+}
+
+func TestStore(t *testing.T) {
+       c, err := Open("teststore")
+       defer c.Close()
+       if err != nil {
+               t.Errorf("open failed: %s", err)
+               return
+       }
+       err = c.Ensure("abc")
+       if err != nil {
+               t.Errorf("ensure failed: %s", err)
+               return
+       }
+       for k, v := range testData {
+               err = c.Set("abc", k, []byte(v))
+               if err != nil {
+                       t.Errorf("Set failed: %s", err)
+               }
+       }
+       for k, v := range testData {
+               d, err := c.Get("abc", k)
+               if err != nil {
+                       t.Errorf("Get failed: %s", err)
+                       continue
+               }
+               if string(d) != v {
+                       t.Errorf("expected %s; got %s", v, string(d))
+               }
+       }
+}
+
+func TestPersist(t *testing.T) {
+       c, err := Open("teststore")
+       if err != nil {
+               t.Errorf("open failed: %s", err)
+               return
+       }
+       err = c.Ensure("abc")
+       if err != nil {
+               t.Errorf("ensure failed: %s", err)
+               return
+       }
+       for k, v := range testData {
+               err = c.Set("abc", k, []byte(v))
+               if err != nil {
+                       t.Errorf("Set failed: %s", err)
+               }
+       }
+       c.Close()
+
+       c, err = Open("teststore")
+       if err != nil {
+               t.Errorf("open failed: %s", err)
+               return
+       }
+       for k, v := range testData {
+               d, err := c.Get("abc", k)
+               if err != nil {
+                       t.Errorf("Get failed: %s", err)
+                       continue
+               }
+               if string(d) != v {
+                       t.Errorf("expected %s; got %s", v, string(d))
+               }
+       }
+}