Massive overhaul

This commit is contained in:
kayos@tcp.direct 2022-07-25 21:36:46 -07:00
parent 435d1251ad
commit 73e2b6a7a6
Signed by: kayos
GPG Key ID: 4B841471B4BEE979
16 changed files with 356 additions and 233 deletions

@ -132,14 +132,14 @@ Store is an implmentation of a Filer and a Searcher using Bitcask.
#### func (Store) AllKeys
```go
func (c Store) AllKeys() (keys [][]byte)
func (s Store) AllKeys() (keys [][]byte)
```
AllKeys will return all keys in the database as a slice of byte slices.
#### func (Store) PrefixScan
```go
func (c Store) PrefixScan(prefix string) ([]KeyValue, error)
func (s Store) PrefixScan(prefix string) ([]KeyValue, error)
```
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)
@ -147,7 +147,7 @@ given string and return a map of keys and values. (map[Key]Value)
#### func (Store) Search
```go
func (c Store) Search(query string) ([]KeyValue, error)
func (s Store) Search(query string) ([]KeyValue, error)
```
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)
@ -155,7 +155,7 @@ type casting will be necessary. (e.g: []byte or string)
#### func (Store) ValueExists
```go
func (c Store) ValueExists(value []byte) (key []byte, ok bool)
func (s Store) ValueExists(value []byte) (key []byte, ok bool)
```
ValueExists will check for the existence of a Value anywhere within the
keyspace, returning the Key and true if found, or nil and false if not found.

@ -1,6 +1,7 @@
package bitcask
import (
"errors"
"strings"
"sync"
@ -13,6 +14,12 @@ import (
type Store struct {
*bitcask.Bitcask
database.Searcher
closed bool
}
// Backend returns the underlying bitcask instance.
func (s Store) Backend() any {
return s.Bitcask
}
// DB is a mapper of a Filer and Searcher implementation using Bitcask.
@ -22,6 +29,17 @@ type DB struct {
mu *sync.RWMutex
}
// AllStores returns a list of all bitcask datastores.
func (db *DB) AllStores() []database.Filer {
db.mu.RLock()
defer db.mu.RUnlock()
var stores = make([]database.Filer, len(db.store))
for _, s := range db.store {
stores = append(stores, s)
}
return stores
}
// OpenDB will either open an existing set of bitcask datastores at the given directory, or it will create a new one.
func OpenDB(path string) *DB {
return &DB{
@ -59,7 +77,14 @@ func WithMaxValueSize(size uint64) bitcask.Option {
}
// Init opens a bitcask store at the given path to be referenced by storeName.
func (db *DB) Init(storeName string, bitcaskopts ...bitcask.Option) error {
func (db *DB) Init(storeName string, opts ...any) error {
var bitcaskopts []bitcask.Option
for _, opt := range opts {
if _, ok := opt.(bitcask.Option); !ok {
return errors.New("invalid bitcask option type")
}
bitcaskopts = append(bitcaskopts, opt.(bitcask.Option))
}
db.mu.Lock()
defer db.mu.Unlock()
@ -83,18 +108,18 @@ func (db *DB) Init(storeName string, bitcaskopts ...bitcask.Option) error {
}
// With calls the given underlying bitcask instance.
func (db *DB) With(storeName string) Store {
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 Store{Bitcask: nil}
return nil
}
// WithNew calls the given underlying bitcask instance, if it doesn't exist, it creates it.
func (db *DB) WithNew(storeName string) Store {
func (db *DB) WithNew(storeName string) database.Filer {
db.mu.RLock()
defer db.mu.RUnlock()
d, ok := db.store[storeName]
@ -159,9 +184,9 @@ func (db *DB) withAll(action withAllAction) error {
}
switch action {
case dclose:
err = namedErr(name, db.Close(name))
err = namedErr(name, store.Close())
case dsync:
err = namedErr(name, db.Sync(name))
err = namedErr(name, store.Sync())
default:
return errUnknownAction
}

@ -2,31 +2,42 @@ package bitcask
import (
"strings"
"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 (c Store) Search(query string) ([]KeyValue, error) {
var errs []error
var res []KeyValue
for _, key := range c.AllKeys() {
raw, _ := c.Get(key)
k := Key{b: key}
v := Value{b: raw}
if strings.Contains(string(raw), query) {
res = append(res, KeyValue{Key: k, Value: v})
func (s 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 s.AllKeys() {
raw, err := s.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 res, compoundErrors(errs)
}()
return resChan, errChan
}
// ValueExists will check for the existence of a Value anywhere within the keyspace, returning the Key and true if found, or nil and false if not found.
func (c Store) ValueExists(value []byte) (key []byte, ok bool) {
func (s Store) ValueExists(value []byte) (key []byte, ok bool) {
var raw []byte
var needle = Value{b: value}
for _, k := range c.AllKeys() {
raw, _ = c.Get(k)
v := Value{b: raw}
var needle = kv.NewValue(value)
for _, k := range s.AllKeys() {
raw, _ = s.Get(k)
v := kv.NewValue(raw)
if v.Equal(needle) {
ok = true
return
@ -37,22 +48,36 @@ func (c Store) ValueExists(value []byte) (key []byte, ok bool) {
// 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)
func (c Store) PrefixScan(prefix string) ([]KeyValue, error) {
var res []KeyValue
err := c.Scan([]byte(prefix), func(key []byte) error {
raw, _ := c.Get(key)
k := Key{b: key}
kv := KeyValue{Key: k, Value: Value{b: raw}}
res = append(res, kv)
return nil
})
return res, err
func (s Store) PrefixScan(prefix string) (<-chan *kv.KeyValue, chan error) {
errChan := make(chan error)
resChan := make(chan *kv.KeyValue, 5)
go func() {
var err error
defer func(e error) {
if e != nil {
errChan <- e
}
close(resChan)
close(errChan)
}(err)
err = s.Scan([]byte(prefix), func(key []byte) error {
raw, err := s.Get(key)
if err != nil {
return err
}
if key != nil && raw != nil {
k := kv.NewKey(key)
resChan <- kv.NewKeyValue(k, kv.NewValue(raw))
}
return nil
})
}()
return resChan, errChan
}
// AllKeys will return all keys in the database as a slice of byte slices.
func (c Store) AllKeys() (keys [][]byte) {
keychan := c.Keys()
func (s Store) AllKeys() (keys [][]byte) {
keychan := s.Keys()
for key := range keychan {
keys = append(keys, key)
}

@ -8,6 +8,9 @@ import (
"testing"
c "git.tcp.direct/kayos/common/entropy"
"github.com/davecgh/go-spew/spew"
"git.tcp.direct/tcp.direct/database/kv"
)
var needle = "yeet"
@ -108,25 +111,31 @@ func Test_Search(t *testing.T) {
db.store["yeet"] = Store{Bitcask: nil}
t.Run("BasicSearch", func(t *testing.T) {
t.Logf("executing search for %s", needle)
results, err := db.With(storename).Search(needle)
if err != nil {
t.Errorf("failed to search: %e", err)
}
resChan, errChan := db.With(storename).Search(needle)
var keys = []int{one, two, three, four, five}
var needed = len(keys)
for _, kv := range results {
keyint, err := strconv.Atoi(kv.Key.String())
if err != nil {
t.Fatalf("failed to convert Key to int: %e", err)
}
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", kv.Key.String(), kv.Value.String())
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)
@ -136,13 +145,19 @@ func Test_Search(t *testing.T) {
t.Run("NoResultsSearch", func(t *testing.T) {
bogus := c.RandStr(55)
t.Logf("executing search for %s", bogus)
results, err := db.With(storename).Search(bogus)
if err != nil {
t.Errorf("failed to search: %e", err)
}
if len(results) > 0 {
t.Errorf("[FAIL] got %d results, wanted 0", len(results))
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))
}
}
})
}
@ -189,40 +204,51 @@ 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 = []KeyValue{
{Key: Key{b: []byte("user:Frickhole")}, Value: Value{b: []byte(c.RandStr(55))}},
{Key: Key{b: []byte("user:Johnson")}, Value: Value{b: []byte(c.RandStr(55))}},
{Key: Key{b: []byte("user:Jackson")}, Value: Value{b: []byte(c.RandStr(55))}},
{Key: Key{b: []byte("user:Frackhole")}, Value: Value{b: []byte(c.RandStr(55))}},
{Key: Key{b: []byte("user:Baboshka")}, Value: Value{b: []byte(c.RandStr(55))}},
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 _, kv := range needles {
err := db.With(storename).Put(kv.Key.Bytes(), kv.Value.Bytes())
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)", kv.Key.String(), kv.Value.String())
t.Logf("added needle with key(value): %s(%s)", combo.Key.String(), combo.Value.String())
}
}
res, err := db.With(storename).PrefixScan("user:")
if err != nil {
t.Errorf("failed to PrefixScan: %e", err)
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(res) != len(needles) {
t.Errorf("[FAIL] Length of results (%d) is not the amount of needles we generated (%d)", len(res), len(needles))
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 _, kv := range res {
for _, result := range results {
for _, ogkv := range needles {
if kv.Key.String() != ogkv.Key.String() {
if result.Key.String() != ogkv.Key.String() {
continue
}
t.Logf("[%s] Found needle key", ogkv.Key.String())
t.Logf("Found needle key: %s", result.Key.String())
keysmatched++
if kv.Value.String() != ogkv.Value.String() {
t.Errorf("[FAIL] values of key %s should have matched. wanted: %s, got: %s", kv.Key.String(), ogkv.Value.String(), kv.Value.String())
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("[%s] Found needle value: %s", ogkv.Key.String(), ogkv.Value.String())
t.Logf("Found needle value: %s", ogkv.Value.String())
}
}
if keysmatched != len(needles) {

@ -96,17 +96,35 @@ func TestDB_Init(t *testing.T) { //nolint:funlen,gocognit,cyclop
}
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.Bitcask == nil {
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) {
if nope := db.With("afsafdassdfqwerty"); nope.Bitcask != nil {
t.Fatalf("[FAIL] got non nil result for nonexistent store: %T, %v", nope, nope)
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")
}
@ -194,7 +212,7 @@ func Test_Close(t *testing.T) {
})
t.Run("AssureClosed", func(t *testing.T) {
for _, d := range oldstores {
if st := db.With(d); st.Bitcask != nil {
if st := db.With(d); st != nil {
t.Fatalf("[FAIL] store %s should have been deleted", d)
}
}

18
bitcask/keeper_test.go Normal file

@ -0,0 +1,18 @@
package bitcask
import (
"testing"
"git.tcp.direct/tcp.direct/database"
)
func needKeeper(keeper database.Keeper) {}
func needFiler(filer database.Filer) {}
func Test_Keeper(t *testing.T) {
needKeeper(OpenDB(""))
}
func Test_Filer(t *testing.T) {
needFiler(OpenDB("").With(""))
}

@ -1,54 +0,0 @@
package bitcask
import (
"bytes"
"git.tcp.direct/tcp.direct/database"
)
// KeyValue represents a key and a value from a key/value store.
type KeyValue struct {
Key Key
Value Value
}
// Key represents a key in a key/value store.
type Key struct {
database.Key
b []byte
}
// Bytes returns the raw byte slice form of the Key.
func (k Key) Bytes() []byte {
return k.b
}
// String returns the string slice form of the Key.
func (k Key) String() string {
return string(k.b)
}
// Equal determines if two keys are equal.
func (k Key) Equal(k2 Key) bool {
return bytes.Equal(k.Bytes(), k2.Bytes())
}
// Value represents a value in a key/value store.
type Value struct {
database.Value
b []byte
}
// Bytes returns the raw byte slice form of the Value.
func (v Value) Bytes() []byte {
return v.b
}
// String returns the string slice form of the Value.
func (v Value) String() string {
return string(v.b)
}
// Equal determines if two values are equal.
func (v Value) Equal(v2 Value) bool {
return bytes.Equal(v.Bytes(), v2.Bytes())
}

@ -1,71 +0,0 @@
package bitcask
import (
"testing"
c "git.tcp.direct/kayos/common/entropy"
)
func Test_Equal(t *testing.T) {
t.Run("ShouldBeEqual", func(t *testing.T) {
v := c.RandStr(55)
kv1 := KeyValue{Key{b: []byte(v)}, Value{b: []byte(v)}}
kv2 := KeyValue{Key{b: []byte(v)}, Value{b: []byte(v)}}
if !kv1.Key.Equal(kv2.Key) {
t.Errorf("[FAIL] Keys not equal: %s, %s", kv1.Key.String(), kv2.Key.String())
} else {
if kv1.Key.String() == kv2.Key.String() {
t.Logf("[+] Keys are equal: %s", kv1.Key.String())
} else {
t.Errorf(
"[FAIL] Equal() passed but strings are not the same! kv1: %s != kv2: %s",
kv1.Key.String(), kv2.Key.String(),
)
}
}
if !kv1.Value.Equal(kv2.Value) {
t.Errorf("[FAIL] Values not equal: %s, %s", kv1.Value.String(), kv2.Value.String())
} else {
if kv1.Value.String() == kv2.Value.String() {
t.Logf("[+] Values are equal: %s", kv1.Value.String())
} else {
t.Errorf(
"[FAIL] Equal() passed but strings are not the same! kv1: %s != kv2: %s",
kv1.Value.String(), kv2.Value.String(),
)
}
}
})
t.Run("ShouldNotBeEqual", func(t *testing.T) {
v1 := c.RandStr(55)
v2 := c.RandStr(55)
kv1 := KeyValue{Key{b: []byte(v1)}, Value{b: []byte(v1)}}
kv2 := KeyValue{Key{b: []byte(v2)}, Value{b: []byte(v2)}}
if kv1.Key.Equal(kv2.Key) {
t.Errorf("[FAIL] Keys are equal: %s, %s", kv1.Key.String(), kv2.Key.String())
} else {
if kv1.Key.String() != kv2.Key.String() {
t.Logf("[+] Keys are not equal: %s", kv1.Key.String())
} else {
t.Errorf(
"[FAIL] Equal() passed but strings are the same! kv1: %s != kv2: %s",
kv1.Key.String(), kv2.Key.String(),
)
}
}
if kv1.Value.Equal(kv2.Value) {
t.Errorf("[FAIL] Values are equal: %s, %s", kv1.Value.String(), kv2.Value.String())
} else {
if kv1.Value.String() != kv2.Value.String() {
t.Logf("[+] Values are not equal: %s", kv1.Value.String())
} else {
t.Errorf(
"[FAIL] Equal() passed but strings are the same! kv1: %s != kv2: %s",
kv1.Value.String(), kv2.Value.String(),
)
}
}
})
}

@ -10,6 +10,9 @@ type Filer interface {
// NOTE: One can easily cast anything to a byte slice. (e.g: []byte("fuckholejones") )
// json.Marshal also returns a byte slice by default ;)
// Backend returns the underlying key/value store.
Backend() any
// Has should return true if the given key has an associated value.
Has(key []byte) bool
// Get should retrieve the byte slice corresponding to the given key, and any associated errors upon failure.
@ -18,4 +21,8 @@ type Filer interface {
Put(key []byte, value []byte) error
// Delete should delete the key and the value associated with the given key, and return an error upon failure.
Delete(key []byte) error
// Close should safely end any Filer operations of the given dataStore and close any relevant handlers.
Close() error
// Sync should take any volatile data and solidify it somehow if relevant. (ram to disk in most cases)
Sync() error
}

1
go.mod

@ -5,6 +5,7 @@ go 1.18
require (
git.tcp.direct/Mirrors/bitcask-mirror v0.0.0-20220228092422-1ec4297c7e34
git.tcp.direct/kayos/common v0.7.0
github.com/davecgh/go-spew v1.1.1
)
require (

@ -6,14 +6,11 @@ type Keeper interface {
// Path should return the base path where all stores should be stored under. (likely as subdirectories)
Path() string
// Init should initialize our Filer at the given path, to be referenced and called by dataStore.
Init(dataStore []byte) error
Init(name string, options ...any) error
// With provides access to the given dataStore by providing a pointer to the related Filer.
With(dataStore []byte) Filer
// Close should safely end any Filer operations of the given dataStore and close any relevant handlers.
Close(dataStore []byte) error
// Sync should take any volatile data and solidify it somehow if relevant. (ram to disk in most cases)
Sync(dataStore []byte) error
With(name string) Store
AllStores() []Filer
// TODO: Backups
CloseAll() error

@ -1,15 +0,0 @@
package database
// Key represents a key in a key/value Filer.
type Key interface {
Bytes() []byte
String() string
Equal(Key) bool
}
// Value represents a value in a key/value Filer.
type Value interface {
Bytes() []byte
String() string
Equal(Value) bool
}

77
kv/keyvalue.go Normal file

@ -0,0 +1,77 @@
package kv
import (
"bytes"
)
// KeyValue represents a key and a value from a key/value store.
type KeyValue struct {
Key *Key
Value *Value
}
// Key represents a key in a key/value store.
type Key struct {
b []byte
}
// NewKey creates a new Key from a byte slice.
func NewKey(data []byte) *Key {
k := Key{b: data}
return &k
}
// NewValue creates a new Value from a byte slice.
func NewValue(data []byte) *Value {
v := Value{b: data}
return &v
}
// NewKeyValue creates a new KeyValue from a key and value.
func NewKeyValue(k *Key, v *Value) *KeyValue {
return &KeyValue{Key: k, Value: v}
}
func (kv *KeyValue) String() string {
return kv.Key.String() + ":" + kv.Value.String()
}
// Equal determines if two key/value pairs are equal.
func (kv *KeyValue) Equal(kv2 *KeyValue) bool {
return kv.Key.Equal(kv2.Key) && kv.Value.Equal(kv2.Value)
}
// Bytes returns the raw byte slice form of the Key.
func (k *Key) Bytes() []byte {
return k.b
}
// String returns the string slice form of the Key.
func (k *Key) String() string {
return string(k.b)
}
// Equal determines if two keys are equal.
func (k *Key) Equal(k2 *Key) bool {
return bytes.Equal(k.Bytes(), k2.Bytes())
}
// Value represents a value in a key/value store.
type Value struct {
b []byte
}
// Bytes returns the raw byte slice form of the Value.
func (v *Value) Bytes() []byte {
return v.b
}
// String returns the string slice form of the Value.
func (v *Value) String() string {
return string(v.b)
}
// Equal determines if two values are equal.
func (v *Value) Equal(v2 *Value) bool {
return bytes.Equal(v.Bytes(), v2.Bytes())
}

61
kv/keyvalue_test.go Normal file

@ -0,0 +1,61 @@
package kv
import (
"testing"
c "git.tcp.direct/kayos/common/entropy"
)
func Test_Equal(t *testing.T) {
t.Run("ShouldBeEqual", func(t *testing.T) {
v := c.RandStr(55)
kv1 := NewKeyValue(NewKey([]byte(v)), NewValue([]byte(v)))
kv2 := NewKeyValue(NewKey([]byte(v)), NewValue([]byte(v)))
if !kv1.Key.Equal(kv2.Key) {
t.Fatalf("[FAIL] Keys not equal: %s, %s", kv1.Key.String(), kv2.Key.String())
}
if kv1.Key.String() != kv2.Key.String() {
t.Fatalf(
"[FAIL] Equal() passed but strings are not the same! kv1: %s != kv2: %s",
kv1.Key.String(), kv2.Key.String())
}
if !kv1.Equal(kv2) {
t.Fatal("[FAIL] KeyValue.Equal failed")
}
t.Logf("[+] KeyValues are equal: %s == %s", kv1.String(), kv2.String())
if !kv1.Value.Equal(kv2.Value) {
t.Fatalf("[FAIL] Values not equal: %s, %s", kv1.Value.String(), kv2.Value.String())
}
if kv1.Value.String() == kv2.Value.String() {
t.Logf("[+] Values are equal: %s", kv1.Value.String())
} else {
t.Errorf(
"[FAIL] Equal() passed but strings are not the same! kv1: %s != kv2: %s",
kv1.Value.String(), kv2.Value.String(),
)
}
})
t.Run("ShouldNotBeEqual", func(t *testing.T) {
v1 := c.RandStr(55)
v2 := c.RandStr(55)
kv1 := NewKeyValue(NewKey([]byte(v1)), NewValue([]byte(v1)))
kv2 := NewKeyValue(NewKey([]byte(v2)), NewValue([]byte(v2)))
if kv1.Key.Equal(kv2.Key) {
t.Fatalf("[FAIL] Keys are equal: %s, %s", kv1.Key.String(), kv2.Key.String())
}
if kv1.Key.String() == kv2.Key.String() {
t.Fatalf("[FAIL] Equal() did not pass but strings are the same! kv1: %s == kv2: %s",
kv1.Key.String(), kv2.Key.String())
}
t.Logf("[+] Keys are not equal: %s", kv1.Key.String())
if kv1.Value.Equal(kv2.Value) {
t.Fatalf("[FAIL] Values are equal: %s, %s", kv1.Value.String(), kv2.Value.String())
}
if kv1.Value.String() == kv2.Value.String() {
t.Fatalf("[FAIL] Equal() passed but strings are the same! kv1: %s != kv2: %s",
kv1.Value.String(), kv2.Value.String())
}
t.Logf("[+] Values are not equal: %s", kv1.Value.String())
})
}

@ -1,13 +1,15 @@
package database
import "git.tcp.direct/tcp.direct/database/kv"
// Searcher must be able to search through our datastore(s) with strings.
type Searcher interface {
// AllKeys must retrieve all keys in the datastore with the given storeName.
AllKeys() []string
// PrefixScan must return all keys that begin with the given prefix.
PrefixScan(prefix string) map[string]interface{}
// Search must be able to search through the contents of our database and return a map of results.
Search(query string) map[string]interface{}
AllKeys() [][]byte
// PrefixScan must retrieve all keys in the datastore and stream them to the given channel.
PrefixScan(prefix string) (<-chan *kv.KeyValue, chan error)
// Search must be able to search through the value contents of our database and stream the results to the given channel.
Search(query string) (<-chan *kv.KeyValue, chan error)
// ValueExists searches for an exact match of the given value and returns the key that contains it.
ValueExists(value []byte) (key []byte, ok bool)
}

6
store.go Normal file

@ -0,0 +1,6 @@
package database
type Store interface {
Filer
Searcher
}