diff --git a/bitcask/bitcask.go b/bitcask/bitcask.go index 62cbdb5..5caabdf 100644 --- a/bitcask/bitcask.go +++ b/bitcask/bitcask.go @@ -2,6 +2,8 @@ package bitcask import ( "errors" + "fmt" + "io/fs" "strings" "sync" @@ -119,9 +121,16 @@ func (db *DB) With(storeName string) database.Store { } // WithNew calls the given underlying bitcask instance, if it doesn't exist, it creates it. -func (db *DB) WithNew(storeName string) database.Filer { +func (db *DB) WithNew(storeName string, opts ...any) database.Filer { db.mu.RLock() defer db.mu.RUnlock() + for _, opt := range opts { + if _, ok := opt.(bitcask.Option); !ok { + fmt.Println("invalid bitcask option type: ", opt) + continue + } + defaultBitcaskOptions = append(defaultBitcaskOptions, opt.(bitcask.Option)) + } d, ok := db.store[storeName] if ok { return d @@ -132,6 +141,7 @@ func (db *DB) WithNew(storeName string) database.Filer { if err == nil { return db.store[storeName] } + fmt.Println("error creating bitcask store: ", err) return Store{Bitcask: nil} } @@ -184,7 +194,11 @@ func (db *DB) withAll(action withAllAction) error { } switch action { case dclose: - err = namedErr(name, store.Close()) + closeErr := store.Close() + if errors.Is(closeErr, fs.ErrClosed) { + continue + } + err = namedErr(name, closeErr) case dsync: err = namedErr(name, store.Sync()) default: diff --git a/bitcask/bitcask_test.go b/bitcask/bitcask_test.go index 440ebda..f8475a6 100644 --- a/bitcask/bitcask_test.go +++ b/bitcask/bitcask_test.go @@ -3,14 +3,18 @@ package bitcask import ( "bytes" "errors" + "io/fs" "os" "strings" "testing" c "git.tcp.direct/kayos/common/entropy" + "github.com/davecgh/go-spew/spew" + + "git.tcp.direct/tcp.direct/database" ) -func newTestDB(t *testing.T) *DB { +func newTestDB(t *testing.T) database.Keeper { t.Helper() tpath := t.TempDir() tdb := OpenDB(tpath) @@ -20,11 +24,12 @@ func newTestDB(t *testing.T) *DB { return tdb } -func seedRandKV(db *DB, store string) error { +func seedRandKV(db database.Keeper, store string) error { return db.With(store).Put([]byte(c.RandStr(55)), []byte(c.RandStr(55))) } -func seedRandStores(db *DB, t *testing.T) { +func seedRandStores(db database.Keeper, t *testing.T) { + t.Helper() for n := 0; n != 5; n++ { randstore := c.RandStr(5) err := db.Init(randstore) @@ -130,10 +135,10 @@ func TestDB_Init(t *testing.T) { //nolint:funlen,gocognit,cyclop } }) t.Run("syncAllShouldFail", func(t *testing.T) { - db.store["wtf"] = Store{} + db.(*DB).store["wtf"] = Store{} t.Cleanup(func() { t.Logf("deleting bogus store map entry") - delete(db.store, "wtf") + delete(db.(*DB).store, "wtf") }) err := db.SyncAll() if err == nil { @@ -179,7 +184,7 @@ func Test_Sync(t *testing.T) { var db = newTestDB(t) seedRandStores(db, t) t.Run("Sync", func(t *testing.T) { - for d := range db.store { + for d := range db.(*DB).store { err := db.With(d).Sync() if err != nil { t.Errorf("[FAIL] failed to sync %s: %e", d, err) @@ -198,23 +203,23 @@ func Test_Close(t *testing.T) { seedRandStores(db, t) var oldstores []string t.Run("Close", func(t *testing.T) { - for d := range db.store { + for d := range db.AllStores() { oldstores = append(oldstores, d) err := db.Close(d) if err != nil { - t.Fatalf("[FAIL] failed to close %s: %e", d, err) - } else { - t.Logf("[+] Close() successful for %s", d) + t.Fatalf("[FAIL] failed to close %s: %v", d, err) } + t.Logf("[+] Close() successful for %s", d) } - }) - t.Run("AssureClosed", func(t *testing.T) { - for _, d := range oldstores { - if st := db.With(d); st != nil { - t.Fatalf("[FAIL] store %s should have been deleted", d) + t.Run("AssureClosed", func(t *testing.T) { + for _, d := range oldstores { + if st := db.With(d); st != nil { + spew.Dump(st) + t.Errorf("[FAIL] store %s should have been deleted", d) + } } - } - t.Logf("[SUCCESS] Confirmed that all stores have been closed") + t.Logf("[SUCCESS] Confirmed that all stores have been closed") + }) }) t.Run("CantCloseBogusStore", func(t *testing.T) { @@ -227,27 +232,34 @@ func Test_Close(t *testing.T) { func Test_withAll(t *testing.T) { var db = newTestDB(t) - defer db.CloseAll() + asdf1 := c.RandStr(10) + asdf2 := c.RandStr(10) + + defer func() { + if err := db.CloseAll(); err != nil && !errors.Is(err, fs.ErrClosed) { + t.Errorf("[FAIL] failed to close all stores: %v", err) + } + }() t.Run("withAllNoStores", func(t *testing.T) { - err := db.withAll(121) + err := db.(*DB).withAll(121) if !errors.Is(err, ErrNoStores) { t.Errorf("[FAIL] got err %e, wanted err %e", err, ErrNoStores) } }) t.Run("withAllNilMap", func(t *testing.T) { nilDb := newTestDB(t) - nilDb.store = nil - err := nilDb.withAll(dclose) + nilDb.(*DB).store = nil + err := nilDb.(*DB).withAll(dclose) if err == nil { t.Errorf("[FAIL] got nil err from trying to work on nil map, wanted err") } }) t.Run("withAllBogusAction", func(t *testing.T) { - err := db.Init("asdf") + err := db.Init(asdf1) if err != nil { t.Errorf("[FAIL] unexpected error: %e", err) } - wAllErr := db.withAll(121) + wAllErr := db.(*DB).withAll(121) if !errors.Is(wAllErr, ErrUnknownAction) { t.Errorf("[FAIL] wanted error %e, got error %e", ErrUnknownAction, err) } @@ -266,20 +278,20 @@ func Test_withAll(t *testing.T) { } t.Logf("[+] found store named %s: %v", n, s) } - if len(allStores) != len(db.store) { - t.Errorf("[FAIL] found %d stores, expected %d", len(allStores), len(db.store)) + if len(allStores) != len(db.(*DB).store) { + t.Errorf("[FAIL] found %d stores, expected %d", len(allStores), len(db.(*DB).store)) } }) t.Run("ListAllAndInteract", func(t *testing.T) { - err := db.Init("asdf2") + err := db.Init(asdf2) if err != nil { t.Errorf("[FAIL] unexpected error: %e", err) } - err = db.With("asdf").Put([]byte("asdf"), []byte("asdf")) + err = db.With(asdf1).Put([]byte("asdf"), []byte("asdf")) if err != nil { t.Errorf("[FAIL] unexpected error: %e", err) } - err = db.With("asdf2").Put([]byte("asdf2"), []byte("asdf2")) + err = db.With(asdf2).Put([]byte("asdf"), []byte("asdf")) if err != nil { t.Errorf("[FAIL] unexpected error: %e", err) } @@ -294,19 +306,19 @@ func Test_withAll(t *testing.T) { if s == nil { t.Errorf("[FAIL] store is nil") } - if len(db.store) != 2 { - t.Errorf("[SANITY FAIL] found %d stores, expected %d", len(allStores), len(db.store)) + if len(db.(*DB).store) != 2 { + t.Errorf("[SANITY FAIL] found %d stores, expected %d", len(allStores), len(db.(*DB).store)) } t.Logf("[+] found store named %s: %v", n, s) - if len(allStores) != len(db.store) { - t.Errorf("[FAIL] found %d stores, expected %d", len(allStores), len(db.store)) + if len(allStores) != len(db.(*DB).store) { + t.Errorf("[FAIL] found %d stores, expected %d", len(allStores), len(db.(*DB).store)) } var res []byte - res, err = db.With(n).Get([]byte(n)) + res, err = db.With(n).Get([]byte("asdf")) if err != nil { - t.Errorf("[FAIL] unexpected error: %e", err) + t.Errorf("[FAIL] unexpected error: %v", err) } - if !bytes.Equal(res, []byte(n)) { + if !bytes.Equal(res, []byte("asdf")) { t.Errorf("[FAIL] expected %s, got %s", n, res) } else { t.Logf("[+] found %s in store %s", res, n) @@ -314,14 +326,12 @@ func Test_withAll(t *testing.T) { } }) t.Run("WithAllIncludingBadStore", func(t *testing.T) { - db.store["yeeterson"] = Store{} - err := db.withAll(dclose) - if err != nil { - t.Logf(err.Error()) - } + db.(*DB).store["yeeterson"] = Store{} + err := db.(*DB).withAll(dclose) if err == nil { t.Errorf("[FAIL] got nil err, wanted any error") } + delete(db.(*DB).store, "yeeterson") }) } diff --git a/go.mod b/go.mod index 137562c..c96cffa 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( git.tcp.direct/Mirrors/bitcask-mirror v0.0.0-20220228092422-1ec4297c7e34 - git.tcp.direct/kayos/common v0.8.2 + git.tcp.direct/kayos/common v0.8.6 github.com/davecgh/go-spew v1.1.1 github.com/hashicorp/go-multierror v1.1.1 ) @@ -17,6 +17,6 @@ require ( github.com/plar/go-adaptive-radix-tree v1.0.4 // indirect github.com/rs/zerolog v1.26.1 // indirect golang.org/x/exp v0.0.0-20200228211341-fcea875c7e85 // indirect - golang.org/x/sys v0.6.0 // indirect + golang.org/x/sys v0.8.0 // indirect nullprogram.com/x/rng v1.1.0 // indirect ) diff --git a/go.sum b/go.sum index cfd6e94..6fdbed2 100644 --- a/go.sum +++ b/go.sum @@ -41,6 +41,8 @@ git.tcp.direct/Mirrors/bitcask-mirror v0.0.0-20220228092422-1ec4297c7e34 h1:tvoL git.tcp.direct/Mirrors/bitcask-mirror v0.0.0-20220228092422-1ec4297c7e34/go.mod h1:NX/Gqm/iy6Tg2CfcmmB/kW/qzKKrGR6o2koOKA5KWnc= git.tcp.direct/kayos/common v0.8.2 h1:Usev4zpFLRFGTjQ+5vhReYSS/3+XGOgYbVufIWqMMW8= git.tcp.direct/kayos/common v0.8.2/go.mod h1:1XroljMDSTRybzyFGPTxs0gVb45YtH7wcehelU4koW8= +git.tcp.direct/kayos/common v0.8.6 h1:lt8nv+PrgAcbiOnbKUt7diza5hifR5fV3un6uIp/YVc= +git.tcp.direct/kayos/common v0.8.6/go.mod h1:QGGn7d2l4xBG7Cs/g84JzItPpHWjtfiyy+PSMnf6TzE= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -488,6 +490,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/keeper.go b/keeper.go index 20338c2..31ddee9 100644 --- a/keeper.go +++ b/keeper.go @@ -10,9 +10,15 @@ type Keeper interface { // With provides access to the given dataStore by providing a pointer to the related Filer. With(name string) Store + // WithNew should initialize a new Filer at the given path. + WithNew(name string, options ...any) Filer + AllStores() map[string]Filer // TODO: Backups + Close(name string) error + CloseAll() error SyncAll() error + SyncAndCloseAll() error }