Fix: remove premature pogreb add
This commit is contained in:
parent
0d6298995f
commit
509f844f49
@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 kayos (kayos@tcp.direct)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -1,33 +0,0 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
)
|
||||
|
||||
//goland:noinspection GoExportedElementShouldHaveComment
|
||||
var (
|
||||
ErrUnknownAction = errors.New("unknown action")
|
||||
ErrBogusStore = errors.New("bogus store backend")
|
||||
ErrBadOptions = errors.New("invalid pogreb options")
|
||||
ErrStoreExists = errors.New("store name already exists")
|
||||
ErrNoStores = errors.New("no stores initialized")
|
||||
)
|
||||
|
||||
func namedErr(name string, err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return multierror.Prefix(err, name)
|
||||
}
|
||||
|
||||
func compoundErrors(errs []error) (err error) {
|
||||
for _, e := range errs {
|
||||
if e == nil {
|
||||
continue
|
||||
}
|
||||
err = multierror.Append(err, e)
|
||||
}
|
||||
return
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.tcp.direct/tcp.direct/database"
|
||||
)
|
||||
|
||||
func Test_Interfaces(t *testing.T) {
|
||||
v := OpenDB(t.TempDir())
|
||||
var keeper interface{} = v
|
||||
if _, ok := keeper.(database.Keeper); !ok {
|
||||
t.Error("Keeper interface not implemented")
|
||||
} else {
|
||||
t.Log("Keeper interface implemented")
|
||||
}
|
||||
vs := v.WithNew("test")
|
||||
var searcher interface{} = vs
|
||||
if _, ok := searcher.(database.Searcher); !ok {
|
||||
t.Error("Searcher interface not implemented")
|
||||
} else {
|
||||
t.Log("Searcher interface implemented")
|
||||
}
|
||||
var filer interface{} = vs
|
||||
if _, ok := filer.(database.Filer); !ok {
|
||||
t.Error("Filer interface not implemented")
|
||||
} else {
|
||||
t.Log("Filer interface implemented")
|
||||
}
|
||||
}
|
306
pogreb/pogreb.go
306
pogreb/pogreb.go
@ -1,306 +0,0 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/akrylysov/pogreb"
|
||||
|
||||
"git.tcp.direct/tcp.direct/database"
|
||||
)
|
||||
|
||||
type Option func(*WrappedOptions)
|
||||
|
||||
var OptionAllowRecovery = func(opts *WrappedOptions) {
|
||||
opts.AllowRecovery = true
|
||||
}
|
||||
|
||||
func AllowRecovery() Option {
|
||||
return OptionAllowRecovery
|
||||
}
|
||||
|
||||
func SetPogrebOptions(options pogreb.Options) Option {
|
||||
return func(opts *WrappedOptions) {
|
||||
opts.Options = &options
|
||||
}
|
||||
}
|
||||
|
||||
type WrappedOptions struct {
|
||||
*pogreb.Options
|
||||
// AllowRecovery allows the database to be recovered if a lockfile is detected upon running Init.
|
||||
AllowRecovery bool
|
||||
}
|
||||
|
||||
func (pstore *Store) Len() int {
|
||||
return int(pstore.DB.Count())
|
||||
}
|
||||
|
||||
func (pstore *Store) Keys() [][]byte {
|
||||
iter := pstore.DB.Items()
|
||||
ks := make([][]byte, pstore.DB.Count())
|
||||
for k, _, _ := iter.Next(); k != nil; k, _, _ = iter.Next() {
|
||||
ks = append(ks, k)
|
||||
}
|
||||
return ks
|
||||
}
|
||||
|
||||
func (pstore *Store) Has(key []byte) bool {
|
||||
ok, err := pstore.DB.Has(key)
|
||||
if err != nil {
|
||||
_, _ = os.Stderr.WriteString("error checking pogreb store for key: " + err.Error())
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// Store is an implmentation of a Filer and a Searcher using Bitcask.
|
||||
type Store struct {
|
||||
*pogreb.DB
|
||||
database.Searcher
|
||||
opts *WrappedOptions
|
||||
closed bool
|
||||
}
|
||||
|
||||
// Backend returns the underlying pogreb instance.
|
||||
func (pstore *Store) Backend() any {
|
||||
return pstore.DB
|
||||
}
|
||||
|
||||
// DB is a mapper of a Filer and Searcher implementation using pogreb.
|
||||
type DB struct {
|
||||
store map[string]database.Store
|
||||
path string
|
||||
mu *sync.RWMutex
|
||||
}
|
||||
|
||||
// AllStores returns a map of the names of all pogreb datastores and the corresponding Filers.
|
||||
func (db *DB) AllStores() map[string]database.Filer {
|
||||
db.mu.RLock()
|
||||
defer db.mu.RUnlock()
|
||||
var stores = make(map[string]database.Filer)
|
||||
for n, s := range db.store {
|
||||
stores[n] = s
|
||||
}
|
||||
return stores
|
||||
}
|
||||
|
||||
// FIXME: not returning the error is probably pretty irresponsible.
|
||||
|
||||
// OpenDB will either open an existing set of pogreb datastores at the given directory, or it will create a new one.
|
||||
func OpenDB(path string) *DB {
|
||||
db := &DB{
|
||||
store: make(map[string]database.Store),
|
||||
path: path,
|
||||
mu: &sync.RWMutex{},
|
||||
}
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(path, 0700)
|
||||
if err != nil {
|
||||
_, _ = os.Stderr.WriteString("error creating pogreb directory: " + err.Error())
|
||||
}
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
// Path returns the base path where we store our pogreb "stores".
|
||||
func (db *DB) Path() string {
|
||||
return db.path
|
||||
}
|
||||
|
||||
var defaultPogrebOptions = &WrappedOptions{
|
||||
Options: nil,
|
||||
AllowRecovery: false,
|
||||
}
|
||||
|
||||
// SetDefaultPogrebOptions options will set the options used for all subsequent pogreb stores that are initialized.
|
||||
func SetDefaultPogrebOptions(pogrebopts ...any) {
|
||||
inner, pgoptOk := pogrebopts[0].(pogreb.Options)
|
||||
wrapped, pgoptWrappedOk := pogrebopts[0].(*WrappedOptions)
|
||||
switch {
|
||||
case !pgoptOk && !pgoptWrappedOk:
|
||||
panic("invalid pogreb options")
|
||||
case pgoptOk:
|
||||
defaultPogrebOptions = &WrappedOptions{
|
||||
Options: &inner,
|
||||
AllowRecovery: false,
|
||||
}
|
||||
case pgoptWrappedOk:
|
||||
defaultPogrebOptions = wrapped
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeOptions(opts ...any) *WrappedOptions {
|
||||
var pogrebopts *WrappedOptions
|
||||
pgInner, pgOK := opts[0].(pogreb.Options)
|
||||
pgWrapped, pgWrappedOK := opts[0].(WrappedOptions)
|
||||
switch {
|
||||
case !pgOK && !pgWrappedOK:
|
||||
return nil
|
||||
case pgOK:
|
||||
pogrebopts = &WrappedOptions{
|
||||
Options: &pgInner,
|
||||
AllowRecovery: false,
|
||||
}
|
||||
case pgWrappedOK:
|
||||
pogrebopts = &pgWrapped
|
||||
}
|
||||
return pogrebopts
|
||||
}
|
||||
|
||||
// Init opens a pogreb store at the given path to be referenced by storeName.
|
||||
func (db *DB) Init(storeName string, opts ...any) error {
|
||||
pogrebopts := defaultPogrebOptions
|
||||
if len(opts) > 0 {
|
||||
pogrebopts = normalizeOptions(opts...)
|
||||
if pogrebopts == nil {
|
||||
return ErrBadOptions
|
||||
}
|
||||
}
|
||||
db.mu.Lock()
|
||||
defer db.mu.Unlock()
|
||||
if _, ok := db.store[storeName]; ok {
|
||||
return ErrStoreExists
|
||||
}
|
||||
path := db.Path()
|
||||
if _, err := os.Stat(filepath.Join(path, storeName, "lock")); !os.IsNotExist(err) && !pogrebopts.AllowRecovery {
|
||||
return fmt.Errorf("%w: and seems to be running... "+
|
||||
"Please close it first, or use InitWithRecovery", ErrStoreExists)
|
||||
}
|
||||
c, e := pogreb.Open(filepath.Join(path, storeName), pogrebopts.Options)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
db.store[storeName] = &Store{DB: c}
|
||||
return nil
|
||||
}
|
||||
|
||||
// With calls the given underlying pogreb instance.
|
||||
func (db *DB) With(storeName string) database.Store {
|
||||
db.mu.RLock()
|
||||
defer db.mu.RUnlock()
|
||||
d, ok := db.store[storeName]
|
||||
if ok {
|
||||
return d
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithNew calls the given underlying pogreb instance, if it doesn't exist, it creates it.
|
||||
func (db *DB) WithNew(storeName string, opts ...any) database.Filer {
|
||||
pogrebopts := defaultPogrebOptions
|
||||
if len(opts) > 0 {
|
||||
if pogrebopts = normalizeOptions(opts...); pogrebopts == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
db.mu.RLock()
|
||||
defer db.mu.RUnlock()
|
||||
|
||||
d, ok := db.store[storeName]
|
||||
if ok {
|
||||
return d
|
||||
}
|
||||
db.mu.RUnlock()
|
||||
err := db.Init(storeName, pogrebopts)
|
||||
db.mu.RLock()
|
||||
if err == nil {
|
||||
return db.store[storeName]
|
||||
}
|
||||
_, _ = os.Stderr.WriteString("error creating pogreb store: " + err.Error())
|
||||
return &Store{DB: nil}
|
||||
}
|
||||
|
||||
// Close is a simple shim for pogreb's Close function.
|
||||
func (db *DB) Close(storeName string) error {
|
||||
db.mu.Lock()
|
||||
defer db.mu.Unlock()
|
||||
st, ok := db.store[storeName]
|
||||
if !ok {
|
||||
return ErrBogusStore
|
||||
}
|
||||
err := st.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(db.store, storeName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sync is a simple shim for pogreb's Sync function.
|
||||
func (db *DB) Sync(storeName string) error {
|
||||
db.mu.RLock()
|
||||
defer db.mu.RUnlock()
|
||||
return db.store[storeName].Backend().(*pogreb.DB).Sync()
|
||||
}
|
||||
|
||||
// withAllAction
|
||||
type withAllAction uint8
|
||||
|
||||
const (
|
||||
// dclose
|
||||
dclose withAllAction = iota
|
||||
// dsync
|
||||
dsync
|
||||
)
|
||||
|
||||
// withAll performs an action on all pogreb stores that we have open.
|
||||
// In the case of an error, withAll will continue and return a compound form of any errors that occurred.
|
||||
// For now this is just for Close and Sync, thusly it does a hard lock on the Keeper.
|
||||
func (db *DB) withAll(action withAllAction) error {
|
||||
if db == nil || db.store == nil || len(db.store) < 1 {
|
||||
return ErrNoStores
|
||||
}
|
||||
var errs = make([]error, len(db.store))
|
||||
for name, store := range db.store {
|
||||
var err error
|
||||
if store == nil || store.Backend().(*pogreb.DB) == nil {
|
||||
errs = append(errs, namedErr(name, ErrBogusStore))
|
||||
continue
|
||||
}
|
||||
switch action {
|
||||
case dclose:
|
||||
closeErr := store.Close()
|
||||
if errors.Is(closeErr, fs.ErrClosed) {
|
||||
continue
|
||||
}
|
||||
err = namedErr(name, closeErr)
|
||||
case dsync:
|
||||
err = namedErr(name, store.Sync())
|
||||
default:
|
||||
return ErrUnknownAction
|
||||
}
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return compoundErrors(errs)
|
||||
}
|
||||
|
||||
// SyncAndCloseAll implements the method from Keeper to sync and close all pogreb stores.
|
||||
func (db *DB) SyncAndCloseAll() error {
|
||||
var errs = make([]error, len(db.store))
|
||||
errSync := namedErr("sync", db.SyncAll())
|
||||
if errSync != nil {
|
||||
errs = append(errs, errSync)
|
||||
}
|
||||
errClose := namedErr("close", db.CloseAll())
|
||||
if errClose != nil {
|
||||
errs = append(errs, errClose)
|
||||
}
|
||||
return compoundErrors(errs)
|
||||
}
|
||||
|
||||
// CloseAll closes all pogreb datastores.
|
||||
func (db *DB) CloseAll() error {
|
||||
return db.withAll(dclose)
|
||||
}
|
||||
|
||||
// SyncAll syncs all pogreb datastores.
|
||||
func (db *DB) SyncAll() error {
|
||||
return db.withAll(dsync)
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/akrylysov/pogreb"
|
||||
|
||||
"git.tcp.direct/tcp.direct/database/kv"
|
||||
)
|
||||
|
||||
// Search will search for a given string within all values inside of a Store.
|
||||
// Note, type casting will be necessary. (e.g: []byte or string)
|
||||
func (pstore *Store) Search(query string) (<-chan *kv.KeyValue, chan error) {
|
||||
var errChan = make(chan error)
|
||||
var resChan = make(chan *kv.KeyValue, 5)
|
||||
go func() {
|
||||
defer func() {
|
||||
close(resChan)
|
||||
close(errChan)
|
||||
}()
|
||||
for _, key := range pstore.Keys() {
|
||||
raw, err := pstore.Get(key)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
continue
|
||||
}
|
||||
if raw != nil && strings.Contains(string(raw), query) {
|
||||
keyVal := kv.NewKeyValue(kv.NewKey(key), kv.NewValue(raw))
|
||||
resChan <- keyVal
|
||||
}
|
||||
}
|
||||
}()
|
||||
return resChan, errChan
|
||||
}
|
||||
|
||||
// ValueExists will check for the existence of a Value anywhere within the keyspace;
|
||||
// returning the first Key found, true if found || nil and false if not found.
|
||||
func (pstore *Store) ValueExists(value []byte) (key []byte, ok bool) {
|
||||
var raw []byte
|
||||
var needle = kv.NewValue(value)
|
||||
for _, key = range pstore.Keys() {
|
||||
raw, _ = pstore.Get(key)
|
||||
v := kv.NewValue(raw)
|
||||
if v.Equal(needle) {
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// PrefixScan will scan a Store for all keys that have a matching prefix of the given string
|
||||
// and return a map of keys and values. (map[Key]Value)
|
||||
// error channel will block, so be sure to read from it.
|
||||
func (pstore *Store) PrefixScan(prefixs string) (<-chan *kv.KeyValue, chan error) {
|
||||
prefix := []byte(prefixs)
|
||||
errChan := make(chan error)
|
||||
resChan := make(chan *kv.KeyValue, 5)
|
||||
go func() {
|
||||
var err error
|
||||
defer func(e error) {
|
||||
close(resChan)
|
||||
close(errChan)
|
||||
}(err)
|
||||
iter := pstore.DB.Items()
|
||||
for k, v, iterErr := iter.Next(); k != nil; k, v, iterErr = iter.Next() {
|
||||
if errors.Is(iterErr, pogreb.ErrIterationDone) {
|
||||
break
|
||||
}
|
||||
if iterErr != nil {
|
||||
errChan <- iterErr
|
||||
continue
|
||||
}
|
||||
if bytes.HasPrefix(k, prefix) {
|
||||
resChan <- kv.NewKeyValue(kv.NewKey(k), kv.NewValue(v))
|
||||
}
|
||||
}
|
||||
}()
|
||||
return resChan, errChan
|
||||
}
|
@ -1,265 +0,0 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
c "git.tcp.direct/kayos/common/entropy"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
|
||||
"git.tcp.direct/tcp.direct/database/kv"
|
||||
)
|
||||
|
||||
var needle = "yeet"
|
||||
|
||||
type Foo struct {
|
||||
Bar string
|
||||
Yeet int
|
||||
What map[string]int
|
||||
}
|
||||
|
||||
func setupTest(storename string, t *testing.T) *DB {
|
||||
var db *DB
|
||||
testpath := t.TempDir()
|
||||
|
||||
t.Logf("opening database at %s", testpath)
|
||||
db = OpenDB(testpath)
|
||||
|
||||
err := db.Init(storename)
|
||||
if err != nil {
|
||||
t.Fatalf("[FAIL] couuldn't initialize store: %e", err)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
t.Logf("cleaning up file at; %s", testpath)
|
||||
if err := os.RemoveAll(testpath); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
return db
|
||||
}
|
||||
|
||||
func genJunk(t *testing.T, correct bool) []byte {
|
||||
item := c.RandStr(5)
|
||||
bar := c.RandStr(5)
|
||||
if correct {
|
||||
if c.RNG(100) > 50 {
|
||||
item = needle + c.RandStr(c.RNG(5))
|
||||
} else {
|
||||
bar = c.RandStr(c.RNG(5)) + needle
|
||||
}
|
||||
}
|
||||
|
||||
f := Foo{
|
||||
Bar: bar,
|
||||
Yeet: c.RNG(50),
|
||||
What: map[string]int{c.RandStr(5): 2, item: 7},
|
||||
}
|
||||
|
||||
raw, err := json.Marshal(f)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
func addJunk(db *DB, storename string, one, two, three, four, five int, t *testing.T, echo bool) [][]byte {
|
||||
var needles [][]byte
|
||||
for n := 0; n != 100; n++ {
|
||||
var rawjson []byte
|
||||
switch n {
|
||||
case one, two, three, four, five:
|
||||
rawjson = genJunk(t, true)
|
||||
needles = append(needles, rawjson)
|
||||
default:
|
||||
rawjson = genJunk(t, false)
|
||||
}
|
||||
err := db.With(storename).Put([]byte(fmt.Sprintf("%d", n)), rawjson)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
t.Logf("%e", err)
|
||||
}
|
||||
}
|
||||
if echo {
|
||||
t.Logf(
|
||||
"created 100 entries of random data with needles located at %d, %d, %d, %d, %d",
|
||||
one, two, three, four, five,
|
||||
)
|
||||
} else {
|
||||
t.Log("created 100 entries of junk")
|
||||
}
|
||||
|
||||
return needles
|
||||
}
|
||||
|
||||
func Test_Search(t *testing.T) {
|
||||
var storename = "test_search"
|
||||
var db = setupTest(storename, t)
|
||||
|
||||
one := c.RNG(100)
|
||||
two := c.RNG(100)
|
||||
three := c.RNG(100)
|
||||
four := c.RNG(100)
|
||||
five := c.RNG(100)
|
||||
|
||||
addJunk(db, storename, one, two, three, four, five, t, true)
|
||||
|
||||
// For coverage
|
||||
db.store["yeet"] = &Store{DB: nil}
|
||||
t.Run("BasicSearch", func(t *testing.T) {
|
||||
t.Logf("executing search for %s", needle)
|
||||
resChan, errChan := db.With(storename).Search(needle)
|
||||
var keys = []int{one, two, three, four, five}
|
||||
var needed = len(keys)
|
||||
|
||||
for keyValue := range resChan {
|
||||
keyint, err := strconv.Atoi(keyValue.Key.String())
|
||||
for _, k := range keys {
|
||||
if keyint == k {
|
||||
needed--
|
||||
}
|
||||
}
|
||||
keys = append(keys, keyint)
|
||||
t.Logf("Found Key: %s, Value: %s", keyValue.Key.String(), keyValue.Value.String())
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("failed to convert Key to int: %e", err)
|
||||
}
|
||||
select {
|
||||
case err := <-errChan:
|
||||
if err != nil {
|
||||
t.Fatalf("failed to search: %e", err)
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
if needed != 0 {
|
||||
t.Errorf("Needed %d results, got %d", len(keys), len(keys)-needed)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("NoResultsSearch", func(t *testing.T) {
|
||||
bogus := c.RandStr(55)
|
||||
t.Logf("executing search for %s", bogus)
|
||||
var results []*kv.KeyValue
|
||||
resChan, errChan := db.With(storename).Search(bogus)
|
||||
select {
|
||||
case err := <-errChan:
|
||||
t.Errorf("failed to search: %s", err.Error())
|
||||
case r := <-resChan:
|
||||
if r != nil {
|
||||
spew.Dump(r)
|
||||
results = append(results, r)
|
||||
}
|
||||
if len(results) > 0 {
|
||||
t.Errorf("[FAIL] got %d results, wanted 0", len(results))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ValueExists(t *testing.T) {
|
||||
var storename = "test_value_exists"
|
||||
var db = setupTest(storename, t)
|
||||
|
||||
t.Run("ValueExists", func(t *testing.T) {
|
||||
needles := addJunk(db, storename, c.RNG(100), c.RNG(100), c.RNG(100), c.RNG(100), c.RNG(100), t, true)
|
||||
|
||||
for _, ndl := range needles {
|
||||
k, exists := db.With(storename).ValueExists(ndl)
|
||||
if !exists {
|
||||
t.Fatalf("[FAIL] store should have contained a value %s somewhere, it did not.", string(ndl))
|
||||
}
|
||||
if k == nil {
|
||||
t.Fatalf("[FAIL] store should have contained a value %s somewhere, "+
|
||||
"it said it did but key was nil", string(ndl))
|
||||
}
|
||||
v, _ := db.With(storename).Get(k)
|
||||
if string(v) != string(ndl) {
|
||||
t.Fatalf("[FAIL] retrieved value does not match search target %s != %s", string(v), string(ndl))
|
||||
}
|
||||
t.Logf("[SUCCESS] successfully located value: %s, at key: %s", string(k), string(v))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ValueShouldNotExist", func(t *testing.T) {
|
||||
for n := 0; n != 5; n++ {
|
||||
garbage := c.RandStr(55)
|
||||
if _, exists := db.With(storename).ValueExists([]byte(garbage)); exists {
|
||||
t.Errorf("[FAIL] store should have not contained value %v, but it did", []byte(garbage))
|
||||
} else {
|
||||
t.Logf("[SUCCESS] store succeeded in not having random value %s", garbage)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ValueExistNilBitcask", func(t *testing.T) {
|
||||
db.store["asdb"] = &Store{DB: nil}
|
||||
garbage := "yeet"
|
||||
if _, exists := db.With(storename).ValueExists([]byte(garbage)); exists {
|
||||
t.Errorf("[FAIL] store should have not contained value %v, should have been nil", []byte(garbage))
|
||||
} else {
|
||||
t.Log("[SUCCESS] store succeeded in being nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_PrefixScan(t *testing.T) {
|
||||
var storename = "test_prefix_scan"
|
||||
var db = setupTest(storename, t)
|
||||
addJunk(db, storename, c.RNG(5), c.RNG(5), c.RNG(5), c.RNG(5), c.RNG(5), t, false)
|
||||
var needles = []*kv.KeyValue{
|
||||
kv.NewKeyValue(kv.NewKey([]byte("user:Frickhole")), kv.NewValue([]byte(c.RandStr(55)))),
|
||||
kv.NewKeyValue(kv.NewKey([]byte("user:Johnson")), kv.NewValue([]byte(c.RandStr(55)))),
|
||||
kv.NewKeyValue(kv.NewKey([]byte("user:Jackson")), kv.NewValue([]byte(c.RandStr(55)))),
|
||||
kv.NewKeyValue(kv.NewKey([]byte("user:Frackhole")), kv.NewValue([]byte(c.RandStr(55)))),
|
||||
kv.NewKeyValue(kv.NewKey([]byte("user:Baboshka")), kv.NewValue([]byte(c.RandStr(55)))),
|
||||
}
|
||||
for _, combo := range needles {
|
||||
err := db.With(storename).Put(combo.Key.Bytes(), combo.Value.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to add data to %s: %e", storename, err)
|
||||
} else {
|
||||
t.Logf("added needle with key(value): %s(%s)", combo.Key.String(), combo.Value.String())
|
||||
}
|
||||
}
|
||||
resChan, errChan := db.With(storename).PrefixScan("user:")
|
||||
var results []*kv.KeyValue
|
||||
for keyValue := range resChan {
|
||||
results = append(results, keyValue)
|
||||
select {
|
||||
case err := <-errChan:
|
||||
if err != nil {
|
||||
t.Fatalf("failed to PrefixScan: %e", err)
|
||||
}
|
||||
break
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(results) != len(needles) {
|
||||
t.Errorf("[FAIL] Length of results (%d) is not the amount of needles we generated (%d)", len(results), len(needles))
|
||||
}
|
||||
var keysmatched = 0
|
||||
for _, result := range results {
|
||||
for _, ogkv := range needles {
|
||||
if result.Key.String() != ogkv.Key.String() {
|
||||
continue
|
||||
}
|
||||
t.Logf("Found needle key: %s", result.Key.String())
|
||||
keysmatched++
|
||||
if result.Value.String() != ogkv.Value.String() {
|
||||
t.Errorf("[FAIL] values of key %s should have matched. wanted: %s, got: %s",
|
||||
result.Key.String(), ogkv.Value.String(), result.Value.String())
|
||||
}
|
||||
t.Logf("Found needle value: %s", ogkv.Value.String())
|
||||
}
|
||||
}
|
||||
if keysmatched != len(needles) {
|
||||
t.Errorf("Needed to match %d keys, only matched %d", len(needles), len(needles))
|
||||
}
|
||||
}
|
@ -1,378 +0,0 @@
|
||||
package pogreb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"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) 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
|
||||
}
|
||||
|
||||
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) {
|
||||
t.Helper()
|
||||
for n := 0; n != 5; n++ {
|
||||
randstore := c.RandStr(5)
|
||||
err := db.Init(randstore)
|
||||
if err != nil {
|
||||
t.Errorf("failed to initialize store for test SyncAndCloseAll: %e", err)
|
||||
}
|
||||
err = seedRandKV(db, randstore)
|
||||
if err != nil {
|
||||
t.Errorf("failed to initialize random values in store %s for test SyncAndCloseAll: %e", randstore, err)
|
||||
}
|
||||
}
|
||||
t.Logf("seeded random stores with random values for test %s", t.Name())
|
||||
}
|
||||
|
||||
func TestDB_Init(t *testing.T) { //nolint:funlen,gocognit,cyclop
|
||||
var db = newTestDB(t)
|
||||
type args struct{ storeName string }
|
||||
type test struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
specErr error
|
||||
}
|
||||
tests := []test{
|
||||
{
|
||||
name: "simple",
|
||||
args: args{"simple"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "storeExists",
|
||||
args: args{"simple"},
|
||||
wantErr: true,
|
||||
specErr: ErrStoreExists,
|
||||
},
|
||||
{
|
||||
name: "newStore",
|
||||
args: args{"notsimple"},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := db.Init(tt.args.storeName)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("[FAIL] Init() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if (err != nil) != tt.wantErr && tt.specErr != nil && !errors.Is(err, tt.specErr) {
|
||||
t.Errorf("[FAIL] wanted error %e, got error %e", tt.specErr, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("withStoreTest", func(t *testing.T) {
|
||||
key := []byte{51, 50}
|
||||
value := []byte("string")
|
||||
err := db.With("simple").Put(key, value)
|
||||
t.Logf("Put Value %v at Key %v", string(value), key)
|
||||
if err != nil {
|
||||
t.Fatalf("[FAIL] %e", err)
|
||||
}
|
||||
gvalue, gerr := db.With("simple").Get(key)
|
||||
if gerr != nil {
|
||||
t.Fatalf("[FAIL] %e", gerr)
|
||||
}
|
||||
if !bytes.Equal(gvalue, value) {
|
||||
t.Errorf("[FAIL] wanted %v, got %v", string(value), string(gvalue))
|
||||
}
|
||||
t.Logf("Got Value %v at Key %v", string(gvalue), key)
|
||||
})
|
||||
t.Run("withNewStoreDoesExist", func(t *testing.T) {
|
||||
nope := db.WithNew("bing")
|
||||
if err := nope.Put([]byte("key"), []byte("value")); err != nil {
|
||||
t.Fatalf("[FAIL] %e", err)
|
||||
}
|
||||
err := nope.Put([]byte("bing"), []byte("bong"))
|
||||
if err != nil {
|
||||
t.Fatalf("[FAIL] %e", err)
|
||||
}
|
||||
yup := db.WithNew("bing")
|
||||
res, err := yup.Get([]byte("bing"))
|
||||
if err != nil {
|
||||
t.Errorf("[FAIL] %e", err)
|
||||
}
|
||||
if !bytes.Equal(res, []byte("bong")) {
|
||||
t.Errorf("[FAIL] wanted %v, got %v", string([]byte("bong")), string(res))
|
||||
}
|
||||
})
|
||||
t.Run("withNewStoreDoesntExist", func(t *testing.T) {
|
||||
if nope := db.WithNew("asdfqwerty"); nope.Backend() == nil {
|
||||
t.Fatalf("[FAIL] got nil result for nonexistent store when it should have made itself: %T, %v", nope, nope)
|
||||
} else {
|
||||
t.Logf("[SUCCESS] got nil Value for store that doesn't exist")
|
||||
}
|
||||
})
|
||||
t.Run("withStoreDoesntExist", func(t *testing.T) {
|
||||
nope := db.With(c.RandStr(10))
|
||||
if nope != nil {
|
||||
t.Fatalf("[FAIL] got non nil result for nonexistent store: %T, %v", nope.Backend(), nope.Backend())
|
||||
} else {
|
||||
t.Logf("[SUCCESS] got nil Value for store that doesn't exist")
|
||||
}
|
||||
})
|
||||
t.Run("syncAllShouldFail", func(t *testing.T) {
|
||||
db.(*DB).store["wtf"] = &Store{}
|
||||
t.Cleanup(func() {
|
||||
t.Logf("deleting bogus store map entry")
|
||||
delete(db.(*DB).store, "wtf")
|
||||
})
|
||||
err := db.SyncAll()
|
||||
if err == nil {
|
||||
t.Fatalf("[FAIL] we should have gotten an error from bogus store map entry")
|
||||
}
|
||||
t.Logf("[SUCCESS] got compound error: %e", err)
|
||||
})
|
||||
|
||||
// TODO: make sure sync is ACTUALLY sycing instead of only checking for nil err... ( ._. )
|
||||
|
||||
t.Run("syncAll", func(t *testing.T) {
|
||||
err := db.SyncAll()
|
||||
if err != nil {
|
||||
t.Fatalf("[FAIL] got compound error: %e", err)
|
||||
}
|
||||
})
|
||||
t.Run("closeAll", func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
err := os.RemoveAll("./testdata")
|
||||
if err != nil {
|
||||
t.Fatalf("[CLEANUP FAIL] %e", err)
|
||||
}
|
||||
t.Logf("[CLEANUP] cleaned up ./testdata")
|
||||
})
|
||||
err := db.CloseAll()
|
||||
if err != nil {
|
||||
t.Fatalf("[FAIL] got compound error: %e", err)
|
||||
}
|
||||
db = nil
|
||||
})
|
||||
t.Run("SyncAndCloseAll", func(t *testing.T) {
|
||||
db = newTestDB(t)
|
||||
seedRandStores(db, t)
|
||||
err := db.SyncAndCloseAll()
|
||||
if err != nil {
|
||||
t.Errorf("[FAIL] failed to SyncAndCloseAll: %e", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Sync(t *testing.T) {
|
||||
// TODO: make sure sync is ACTUALLY sycing instead of only checking for nil err...
|
||||
var db = newTestDB(t)
|
||||
seedRandStores(db, t)
|
||||
t.Run("Sync", func(t *testing.T) {
|
||||
for d := range db.(*DB).store {
|
||||
err := db.With(d).Sync()
|
||||
if err != nil {
|
||||
t.Errorf("[FAIL] failed to sync %s: %e", d, err)
|
||||
} else {
|
||||
t.Logf("[+] Sync() successful for %s", d)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Close(t *testing.T) {
|
||||
var db = newTestDB(t)
|
||||
defer func() {
|
||||
db = nil
|
||||
}()
|
||||
seedRandStores(db, t)
|
||||
var oldstores []string
|
||||
t.Run("Close", func(t *testing.T) {
|
||||
for d := range db.AllStores() {
|
||||
oldstores = append(oldstores, d)
|
||||
err := db.Close(d)
|
||||
if err != nil {
|
||||
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 {
|
||||
spew.Dump(st)
|
||||
t.Errorf("[FAIL] store %s should have been deleted", d)
|
||||
}
|
||||
}
|
||||
t.Logf("[SUCCESS] Confirmed that all stores have been closed")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("CantCloseBogusStore", func(t *testing.T) {
|
||||
err := db.Close(c.RandStr(55))
|
||||
if !errors.Is(err, ErrBogusStore) {
|
||||
t.Errorf("[FAIL] got err %e, wanted err %e", err, ErrBogusStore)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_withAll(t *testing.T) {
|
||||
var db = newTestDB(t)
|
||||
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.(*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.(*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(asdf1)
|
||||
if err != nil {
|
||||
t.Errorf("[FAIL] unexpected error: %e", err)
|
||||
}
|
||||
wAllErr := db.(*DB).withAll(121)
|
||||
if !errors.Is(wAllErr, ErrUnknownAction) {
|
||||
t.Errorf("[FAIL] wanted error %e, got error %e", ErrUnknownAction, err)
|
||||
}
|
||||
})
|
||||
t.Run("ListAll", func(t *testing.T) {
|
||||
allStores := db.AllStores()
|
||||
if len(allStores) == 0 {
|
||||
t.Errorf("[FAIL] no stores found")
|
||||
}
|
||||
for n, s := range allStores {
|
||||
if n == "" {
|
||||
t.Errorf("[FAIL] store name is empty")
|
||||
}
|
||||
if s == nil {
|
||||
t.Errorf("[FAIL] store is nil")
|
||||
}
|
||||
t.Logf("[+] found store named %s: %v", n, s)
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
t.Errorf("[FAIL] unexpected error: %e", err)
|
||||
}
|
||||
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("asdf"), []byte("asdf"))
|
||||
if err != nil {
|
||||
t.Errorf("[FAIL] unexpected error: %e", err)
|
||||
}
|
||||
allStores := db.AllStores()
|
||||
if len(allStores) == 0 {
|
||||
t.Errorf("[FAIL] no stores found")
|
||||
}
|
||||
for n, s := range allStores {
|
||||
if n == "" {
|
||||
t.Errorf("[FAIL] store name is empty")
|
||||
}
|
||||
if s == nil {
|
||||
t.Errorf("[FAIL] store is nil")
|
||||
}
|
||||
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.(*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("asdf"))
|
||||
if err != nil {
|
||||
t.Errorf("[FAIL] unexpected error: %v", err)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
t.Run("WithAllIncludingBadStore", func(t *testing.T) {
|
||||
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")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_WithOptions(t *testing.T) { //nolint:funlen,gocognit,cyclop
|
||||
tpath := t.TempDir()
|
||||
tdb := OpenDB(tpath)
|
||||
if tdb == nil {
|
||||
t.Fatalf("failed to open testdb at %s, got nil", tpath)
|
||||
}
|
||||
// FIXME: inconsistent with other implementations (bitcask)
|
||||
defer func() {
|
||||
t.Helper()
|
||||
err := tdb.CloseAll()
|
||||
if err == nil {
|
||||
t.Fatalf("[FAIL] was able to close uninitialized store, expected error")
|
||||
}
|
||||
}()
|
||||
t.Run("InitWithBogusOption", func(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
err := db.Init("bogus", "yeet")
|
||||
if err == nil {
|
||||
t.Errorf("[FAIL] Init should have failed with bogus option")
|
||||
}
|
||||
})
|
||||
}
|
||||
func Test_PhonyInit(t *testing.T) {
|
||||
newtmp := t.TempDir()
|
||||
err := os.MkdirAll(newtmp+"/"+t.Name(), 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("[FAIL] failed to create test directory: %e", err)
|
||||
}
|
||||
err = os.Symlink("/dev/null", filepath.Join(newtmp, t.Name(), "lock"))
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
tdb := OpenDB(newtmp)
|
||||
defer func() {
|
||||
_ = tdb.CloseAll()
|
||||
}()
|
||||
err = tdb.Init(t.Name())
|
||||
if err == nil {
|
||||
t.Error("[FAIL] expected error while trying to open a store where lock exists, got nil")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user