From aed641de42c1dd87d34b13609262c7fac0c5dd76 Mon Sep 17 00:00:00 2001 From: "kayos@tcp.direct" Date: Sun, 28 Jan 2024 20:36:59 -0800 Subject: [PATCH] Feat: DB.Discover() --- bitcask/bitcask.go | 39 +++++++++++++++++++++++++++++++++++++ bitcask/bitcask_test.go | 43 ++++++++++++++++++++++++++++++----------- keeper.go | 2 ++ 3 files changed, 73 insertions(+), 11 deletions(-) diff --git a/bitcask/bitcask.go b/bitcask/bitcask.go index 7e678c6..e7a09d7 100644 --- a/bitcask/bitcask.go +++ b/bitcask/bitcask.go @@ -4,6 +4,8 @@ import ( "errors" "fmt" "io/fs" + "os" + "path/filepath" "strings" "sync" @@ -51,6 +53,43 @@ func OpenDB(path string) *DB { } } +// Discover will discover and initialize all existing bitcask stores at the path opened by [OpenDB]. +func (db *DB) Discover() ([]string, error) { + db.mu.Lock() + defer db.mu.Unlock() + stores := make([]string, 0) + errs := make([]error, 0) + if db.store == nil { + db.store = make(map[string]Store) + } + entries, err := fs.ReadDir(os.DirFS(db.path), ".") + if err != nil { + return nil, err + } + for _, entry := range entries { + if !entry.IsDir() { + continue + } + name := entry.Name() + if _, ok := db.store[name]; ok { + continue + } + c, e := bitcask.Open(filepath.Join(db.path, name), defaultBitcaskOptions...) + if e != nil { + errs = append(errs, e) + continue + } + db.store[name] = Store{Bitcask: c} + stores = append(stores, name) + } + + for _, e := range errs { + err = fmt.Errorf("%w: %w", err, e) + } + + return stores, err +} + // Path returns the base path where we store our bitcask "stores". func (db *DB) Path() string { return db.path diff --git a/bitcask/bitcask_test.go b/bitcask/bitcask_test.go index f8475a6..820bf3f 100644 --- a/bitcask/bitcask_test.go +++ b/bitcask/bitcask_test.go @@ -14,24 +14,26 @@ import ( "git.tcp.direct/tcp.direct/database" ) -func newTestDB(t *testing.T) database.Keeper { +func newTestDB(t *testing.T) (string, database.Keeper) { t.Helper() tpath := t.TempDir() tdb := OpenDB(tpath) if tdb == nil { t.Fatalf("failed to open testdb at %s, got nil", tpath) } - return tdb + return tpath, tdb } func seedRandKV(db database.Keeper, store string) error { return db.With(store).Put([]byte(c.RandStr(55)), []byte(c.RandStr(55))) } -func seedRandStores(db database.Keeper, t *testing.T) { +func seedRandStores(db database.Keeper, t *testing.T) []string { t.Helper() + names := make([]string, 0) for n := 0; n != 5; n++ { randstore := c.RandStr(5) + names = append(names, randstore) err := db.Init(randstore) if err != nil { t.Errorf("failed to initialize store for test SyncAndCloseAll: %e", err) @@ -42,10 +44,11 @@ func seedRandStores(db database.Keeper, t *testing.T) { } } t.Logf("seeded random stores with random values for test %s", t.Name()) + return names } func TestDB_Init(t *testing.T) { //nolint:funlen,gocognit,cyclop - var db = newTestDB(t) + var _, db = newTestDB(t) type args struct{ storeName string } type test struct { name string @@ -170,18 +173,36 @@ func TestDB_Init(t *testing.T) { //nolint:funlen,gocognit,cyclop db = nil }) t.Run("SyncAndCloseAll", func(t *testing.T) { - db = newTestDB(t) - seedRandStores(db, t) + var path string + path, db = newTestDB(t) + names := seedRandStores(db, t) err := db.SyncAndCloseAll() if err != nil { t.Errorf("[FAIL] failed to SyncAndCloseAll: %e", err) } + db = OpenDB(path) + found, err := db.(*DB).Discover() + if err != nil { + t.Errorf("[FAIL] failed to discover stores: %e", err) + } + for _, n := range names { + matched := false + for _, f := range found { + if f == n { + matched = true + break + } + } + if !matched { + t.Errorf("[FAIL] failed to find store %s", n) + } + } }) } func Test_Sync(t *testing.T) { // TODO: make sure sync is ACTUALLY sycing instead of only checking for nil err... - var db = newTestDB(t) + var _, db = newTestDB(t) seedRandStores(db, t) t.Run("Sync", func(t *testing.T) { for d := range db.(*DB).store { @@ -196,7 +217,7 @@ func Test_Sync(t *testing.T) { } func Test_Close(t *testing.T) { - var db = newTestDB(t) + var _, db = newTestDB(t) defer func() { db = nil }() @@ -231,7 +252,7 @@ func Test_Close(t *testing.T) { } func Test_withAll(t *testing.T) { - var db = newTestDB(t) + var _, db = newTestDB(t) asdf1 := c.RandStr(10) asdf2 := c.RandStr(10) @@ -247,7 +268,7 @@ func Test_withAll(t *testing.T) { } }) t.Run("withAllNilMap", func(t *testing.T) { - nilDb := newTestDB(t) + _, nilDb := newTestDB(t) nilDb.(*DB).store = nil err := nilDb.(*DB).withAll(dclose) if err == nil { @@ -473,7 +494,7 @@ func Test_WithOptions(t *testing.T) { //nolint:funlen,gocognit,cyclop } }) t.Run("InitWithBogusOption", func(t *testing.T) { - db := newTestDB(t) + _, db := newTestDB(t) err := db.Init("bogus", "yeet") if err == nil { t.Errorf("[FAIL] Init should have failed with bogus option") diff --git a/keeper.go b/keeper.go index 31ddee9..d900fb3 100644 --- a/keeper.go +++ b/keeper.go @@ -13,6 +13,8 @@ type Keeper interface { // WithNew should initialize a new Filer at the given path. WithNew(name string, options ...any) Filer + Discover() ([]string, error) + AllStores() map[string]Filer // TODO: Backups