Skip to content

Commit

Permalink
add buntdb
Browse files Browse the repository at this point in the history
  • Loading branch information
tsawler committed May 6, 2024
1 parent 0dc9dee commit 1cd1e24
Show file tree
Hide file tree
Showing 11 changed files with 1,786 additions and 14 deletions.
21 changes: 12 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,20 @@

# Remember

Package remember provides an easy way to implement a Redis or Badger cache in your Go application.
Package remember provides an easy way to implement a Redis, BuntDB or Badger cache in your Go application.

## Installation
Install it in the usual way:

`go get -u github.com/tsawler/remember`
`go get -u github.com/tsawler/remember/v2`

# Usage
Create an instance of the `remember.Cache` type by using the `remember.New()` function, and optionally
passing it a `remember.Options` variable:
Create an instance of the `remember.Cache` type by using the `remember.New(cacheType string, o ...Options)` function, and optionally
passing it a `remember.Options` variable. `cacheType` can by redis, buntdb, or badger. The second parameter,
o, is optional.

~~~go
cache := remember.New() // Will use default options, suitable for development.
cache, err := remember.New("redis") // Will use default options, suitable for development.
~~~

Or, specifying options:
Expand All @@ -29,7 +30,8 @@ ops := remember.Options{
Password: "some_password" // The password for Redis.
Prefix: "myapp" // A prefix to use for all keys for this client. Useful when multiple clients use the same database.
DB: 0 // Database. Specifying 0 (the default) means use the default database.
BadgerPath "" // The location for the badger database on disk. Defaults to ./badger
BadgerPath: "" // The location for the badger database on disk. Defaults to ./badger
BuntDBPath: "" // The location for the BuntDB database on disk. Use :memory: for in-memory.
}

cache := remember.New(ops)
Expand All @@ -43,7 +45,7 @@ package main
import (
"encoding/gob"
"fmt"
"github.com/tsawler/remember"
"github.com/tsawler/remember/v2"
"log"
"os"
"time"
Expand All @@ -65,8 +67,9 @@ func main() {
// close the database pool when finished.
defer cache.Close()

// Or create & connect to a Badger database. Nothing else changes.
// cache, _ := remember.New("badger", remember.Options{BadgerPath: "./badger"})
// Alternatively, use "badger" or "boltdb". Nothing else changes.
//cache, _ := remember.New("badger")
//cache, _ := remember.New("buntdb")

// Store a simple string in the cache.
fmt.Println("Putting value in the cache with key of foo")
Expand Down
10 changes: 5 additions & 5 deletions remember.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type RedisCache struct {
Prefix string
}

// Options is the type used to configure a RedisCache object.
// Options is the type used to configure a CacheInterface object.
type Options struct {
Server string // The server where Redis exists.
Port string // The port Redis is listening on.
Expand All @@ -46,9 +46,9 @@ type Options struct {
}

// CacheEntry is a map to hold values, so we can serialize them.
type CacheEntry map[string]interface{}
type CacheEntry map[string]any

// New is a factory method which returns an instance of *RedisCache which satisfies the CacheInterface.
// New is a factory method which returns an instance of a CacheInterface.
func New(cacheType string, o ...Options) (CacheInterface, error) {
var ops Options
if len(o) > 0 {
Expand Down Expand Up @@ -227,7 +227,7 @@ func (c *RedisCache) Empty() error {
return nil
}

// encode serializes a CacheEntry for storage in Redis.
// encode serializes a CacheEntry for storage in a cache.
func encode(item CacheEntry) ([]byte, error) {
b := bytes.Buffer{}
e := gob.NewEncoder(&b)
Expand All @@ -238,7 +238,7 @@ func encode(item CacheEntry) ([]byte, error) {
return b.Bytes(), nil
}

// decode deserializes an item into a map[string]interface{}.
// decode deserializes an item from the cache into a map[string]any.
func decode(str string) (CacheEntry, error) {
item := CacheEntry{}
b := bytes.Buffer{}
Expand Down
185 changes: 185 additions & 0 deletions v2/badger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package remember

import (
"github.com/dgraph-io/badger/v3"
"time"
)

// BadgerCache is the type for a Badger database cache.
type BadgerCache struct {
Conn *badger.DB
Prefix string
}

func (b *BadgerCache) Has(str string) bool {
_, err := b.Get(str)
if err != nil {
return false
}
return true
}

// Close closes the badger database.
func (b *BadgerCache) Close() error {
return b.Conn.Close()
}

// Get attempts to retrieve a value from the cache.
func (b *BadgerCache) Get(str string) (any, error) {
var fromCache []byte

err := b.Conn.View(func(txn *badger.Txn) error {
item, err := txn.Get([]byte(str))
if err != nil {
return err
}

err = item.Value(func(val []byte) error {
fromCache = append([]byte{}, val...)
return nil
})
if err != nil {
return err
}
return nil
})
if err != nil {
return nil, err
}

decoded, err := decode(string(fromCache))
if err != nil {
return nil, err
}

item := decoded[str]

return item, nil
}

// Set puts a value into Badger. The final parameter, expires, is optional.
func (b *BadgerCache) Set(str string, value any, expires ...time.Duration) error {
entry := CacheEntry{}

entry[str] = value
encoded, err := encode(entry)
if err != nil {
return err
}

if len(expires) > 0 {
err = b.Conn.Update(func(txn *badger.Txn) error {
e := badger.NewEntry([]byte(str), encoded).WithTTL(expires[0])
err = txn.SetEntry(e)
return err
})
} else {
err = b.Conn.Update(func(txn *badger.Txn) error {
e := badger.NewEntry([]byte(str), encoded)
err = txn.SetEntry(e)
return err
})
}

return nil
}

// Forget removes an item from the cache, by key.
func (b *BadgerCache) Forget(str string) error {
err := b.Conn.Update(func(txn *badger.Txn) error {
err := txn.Delete([]byte(str))
return err
})

return err
}

// EmptyByMatch removes all entries in Redis which have the prefix match.
func (b *BadgerCache) EmptyByMatch(str string) error {
return b.emptyByMatch(str)
}

// Empty removes all entries in Badger.
func (b *BadgerCache) Empty() error {
return b.emptyByMatch("")
}

func (b *BadgerCache) emptyByMatch(str string) error {
deleteKeys := func(keysForDelete [][]byte) error {
if err := b.Conn.Update(func(txn *badger.Txn) error {
for _, key := range keysForDelete {
if err := txn.Delete(key); err != nil {
return err
}
}
return nil
}); err != nil {
return err
}
return nil
}

collectSize := 100000

err := b.Conn.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.AllVersions = false
opts.PrefetchValues = false
it := txn.NewIterator(opts)
defer it.Close()

keysForDelete := make([][]byte, 0, collectSize)
keysCollected := 0

for it.Seek([]byte(str)); it.ValidForPrefix([]byte(str)); it.Next() {
key := it.Item().KeyCopy(nil)
keysForDelete = append(keysForDelete, key)
keysCollected++
if keysCollected == collectSize {
if err := deleteKeys(keysForDelete); err != nil {
return err
}
}
}

if keysCollected > 0 {
if err := deleteKeys(keysForDelete); err != nil {
return err
}
}

return nil
})

return err
}

// GetInt is a convenience method which retrieves a value from the cache, converts it to an int, and returns it.
func (b *BadgerCache) GetInt(key string) (int, error) {
val, err := b.Get(key)
if err != nil {
return 0, err
}

return val.(int), nil
}

// GetString is a convenience method which retrieves a value from the cache and returns it as a string.
func (b *BadgerCache) GetString(key string) (string, error) {
s, err := b.Get(key)
if err != nil {
return "", err
}
return s.(string), nil
}

// GetTime retrieves a value from the cache by the specified key and returns it as time.Time.
func (b *BadgerCache) GetTime(key string) (time.Time, error) {
fromCache, err := b.Get(key)
if err != nil {
return time.Time{}, err
}

t := fromCache.(time.Time)
return t, nil
}
Loading

0 comments on commit 1cd1e24

Please sign in to comment.