-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add the boltdb cache implementation and some tests (#26)
- Loading branch information
Showing
8 changed files
with
302 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters