Refactor: prep for adding pogreb by implementing interfaces

This commit is contained in:
kayos@tcp.direct 2023-08-01 02:51:12 -07:00
parent ff39591cbe
commit c2d7ae8d3e
Signed by: kayos
GPG Key ID: 4B841471B4BEE979
5 changed files with 77 additions and 44 deletions

@ -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:

@ -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")
})
}

4
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
)

3
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=

@ -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
}