Skip to content

Commit

Permalink
Add the boltdb cache implementation and some tests (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
mstmdev committed May 15, 2023
1 parent 6b40008 commit b6745b9
Show file tree
Hide file tree
Showing 8 changed files with 302 additions and 1 deletion.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Current support following cache drivers
| Redis | `github.com/no-src/nscache/redis` | `redis:https://127.0.0.1:6379` |
| BuntDB | `github.com/no-src/nscache/buntdb` | `buntdb:https://:memory:` or `buntdb:https://buntdb.db` |
| Etcd | `github.com/no-src/nscache/etcd` | `etcd:https://127.0.0.1:2379?dial_timeout=5s` |
| BoltDB | `github.com/no-src/nscache/boltdb` | `boltdb:https://boltdb.db` |

For example, initial a memory cache and write, read and remove data.

Expand All @@ -49,6 +50,7 @@ func main() {
log.Error(err, "init cache error")
return
}
defer c.Close()

// write data
k := "hello"
Expand All @@ -68,4 +70,4 @@ func main() {
return
}
}
```
```
48 changes: 48 additions & 0 deletions boltdb/boltdb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package boltdb

import (
"errors"
"net/url"

"github.com/no-src/nscache"
"github.com/no-src/nscache/cache"
"github.com/no-src/nscache/encoding"
"go.etcd.io/bbolt"
)

const (
// DriverName the unique name of the boltdb driver for register
DriverName = "boltdb"
// defaultBucket the default bucket name for boltdb
defaultBucket = "nscache-default"
)

func newCache(conn *url.URL) (nscache.NSCache, error) {
path, bucket, err := parseConnection(conn)
if err != nil {
return nil, err
}
db, err := bbolt.Open(path, 0600, nil)
if err != nil {
return nil, err
}
return cache.NewCache(newStore(db, []byte(bucket), encoding.DefaultSerializer))
}

// parseConnection parse the boltdb connection string
func parseConnection(u *url.URL) (path string, bucket string, err error) {
if u == nil {
bucket = defaultBucket
return path, bucket, errors.New("invalid boltdb connection string")
}
path = u.Host
bucket = u.Query().Get("bucket")
if len(bucket) == 0 {
bucket = defaultBucket
}
return path, bucket, nil
}

func init() {
nscache.Register(DriverName, newCache)
}
19 changes: 19 additions & 0 deletions boltdb/boltdb_benchmark_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package boltdb

import (
"testing"

"github.com/no-src/nscache/internal/testutil"
)

func BenchmarkBoltDBCache_Get(b *testing.B) {
testutil.BenchmarkCacheGet(b, connectionString, expiration)
}

func BenchmarkBoltDBCache_Set(b *testing.B) {
testutil.BenchmarkCacheSet(b, connectionString, expiration)
}

func BenchmarkBoltDBCache_Remove(b *testing.B) {
testutil.BenchmarkCacheRemove(b, connectionString, expiration)
}
48 changes: 48 additions & 0 deletions boltdb/boltdb_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package boltdb

import (
"net/url"
"testing"

"github.com/no-src/nscache/internal/testutil"
)

var (
connectionString = testutil.BoltDBConnectionString
connectionStringWithBucket = testutil.BoltDBConnectionString + "?bucket=my-bucket"
expiration = testutil.DefaultExpiration
)

func TestBoltDBCache(t *testing.T) {
testutil.TestCache(t, connectionString, expiration)
testutil.TestCache(t, connectionStringWithBucket, expiration)
}

func TestBoltDBCache_NewCache_WithNilURL(t *testing.T) {
_, err := newCache(nil)
if err == nil {
t.Errorf("expect get an error, but get nil")
}
}

func TestBoltDBCache_NewCache_WithInvalidURL(t *testing.T) {
testCases := []struct {
conn string
}{
{"boltdb:https:///invalid"},
{"boltdb:https://"},
}
for _, tc := range testCases {
t.Run(tc.conn, func(t *testing.T) {
u, err := url.Parse(tc.conn)
if err != nil {
t.Errorf("invalid url => %s", tc.conn)
return
}
_, err = newCache(u)
if err == nil {
t.Errorf("expect get an error, but get nil")
}
})
}
}
70 changes: 70 additions & 0 deletions boltdb/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package boltdb

import (
"time"

"github.com/no-src/nscache/encoding"
"github.com/no-src/nscache/store"
"go.etcd.io/bbolt"
)

type boltDBStore struct {
db *bbolt.DB
bucket []byte
serializer encoding.Serializer
}

func newStore(db *bbolt.DB, bucket []byte, serializer encoding.Serializer) store.Store {
return &boltDBStore{
db: db,
bucket: bucket,
serializer: serializer,
}
}

func (s *boltDBStore) Get(k string) *store.Data {
var data []byte
s.db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket(s.bucket)
if b != nil {
data = b.Get([]byte(k))
}
return nil
})
if len(data) == 0 {
return nil
}
var d *store.Data
if s.serializer.Deserialize(data, &d) != nil {
return nil
}
return d
}

func (s *boltDBStore) Set(k string, data []byte, expiration time.Duration) error {
return s.db.Update(func(tx *bbolt.Tx) error {
b, err := tx.CreateBucketIfNotExists(s.bucket)
if err != nil {
return err
}
sd, err := s.serializer.Serialize(store.NewData(data, expiration))
if err != nil {
return err
}
return b.Put([]byte(k), sd)
})
}

func (s *boltDBStore) Remove(k string) error {
return s.db.Update(func(tx *bbolt.Tx) error {
b := tx.Bucket(s.bucket)
if b == nil {
return nil
}
return b.Delete([]byte(k))
})
}

func (s *boltDBStore) Close() error {
return s.db.Close()
}
111 changes: 111 additions & 0 deletions boltdb/store_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package boltdb

import (
"errors"
"testing"
"time"

"github.com/no-src/nscache/encoding"
"go.etcd.io/bbolt"
)

var (
testKey = "hello"
testValue = "world"

errMockSerialize = errors.New("mock serialize error")
errMockDeserialize = errors.New("mock deserialize error")
)

func TestBoltDBStore_RemoveDataInANotExistBucket(t *testing.T) {
db, err := getTestBoltDB()
if err != nil {
t.Errorf("open boltdb error =>%v", err)
return
}
defer db.Close()

s := newStore(db, []byte(defaultBucket), encoding.DefaultSerializer)
err = s.Set(testKey, []byte(testValue), time.Second)
if err != nil {
t.Errorf("add cache data error => %v", err)
return
}
err = db.Update(func(tx *bbolt.Tx) error {
return tx.DeleteBucket([]byte(defaultBucket))
})
if err != nil {
t.Errorf("remove bucket error => %v", err)
return
}
err = s.Remove(testKey)
if err != nil {
t.Errorf("remove cache error => %v", err)
}
}

func TestBoltDBStore_GetDataReturnDeserializeError(t *testing.T) {
db, err := getTestBoltDB()
if err != nil {
t.Errorf("open boltdb error =>%v", err)
return
}
defer db.Close()

s := newStore(db, []byte(defaultBucket), encoding.DefaultSerializer)
err = s.Set(testKey, []byte(testValue), time.Second)
if err != nil {
t.Errorf("add cache data error => %v", err)
return
}
s = newStore(db, []byte(defaultBucket), &mockErrSerializer{})
data := s.Get(testKey)
if data != nil {
t.Errorf("expect to get a nil data, but actual %v", data)
}
}

func TestBoltDBStore_SetDataReturnSerializeError(t *testing.T) {
db, err := getTestBoltDB()
if err != nil {
t.Errorf("open boltdb error =>%v", err)
return
}
defer db.Close()

s := newStore(db, []byte(defaultBucket), &mockErrSerializer{})
err = s.Set(testKey, []byte(testValue), time.Second)
if !errors.Is(err, errMockSerialize) {
t.Errorf("add cache data expect to get an error %v, but actual %v", errMockSerialize, err)
}
}

func TestBoltDBStore_WithEmptyBucket(t *testing.T) {
db, err := getTestBoltDB()
if err != nil {
t.Errorf("open boltdb error =>%v", err)
return
}
defer db.Close()

s := newStore(db, nil, encoding.DefaultSerializer)
err = s.Set(testKey, []byte(testValue), time.Second)
if !errors.Is(err, bbolt.ErrBucketNameRequired) {
t.Errorf("add cache data expect to get an error %v, but actual %v", bbolt.ErrBucketNameRequired, err)
}
}

func getTestBoltDB() (*bbolt.DB, error) {
return bbolt.Open("boltdb_test.db", 0600, nil)
}

type mockErrSerializer struct {
}

func (s *mockErrSerializer) Serialize(v any) ([]byte, error) {
return nil, errMockSerialize
}

func (s *mockErrSerializer) Deserialize(data []byte, v any) error {
return errMockDeserialize
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/no-src/log v0.1.10
github.com/redis/go-redis/v9 v9.0.4
github.com/tidwall/buntdb v1.3.0
go.etcd.io/bbolt v1.3.7
go.etcd.io/etcd/client/v3 v3.5.9
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q09
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.etcd.io/etcd/api/v3 v3.5.9 h1:4wSsluwyTbGGmyjJktOf3wFQoTBIURXHnq9n/G/JQHs=
go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k=
go.etcd.io/etcd/client/pkg/v3 v3.5.9 h1:oidDC4+YEuSIQbsR94rY9gur91UPL6DnxDCIYd2IGsE=
Expand Down

0 comments on commit b6745b9

Please sign in to comment.