✏ refactor storage

This commit is contained in:
Fenny
2020-11-04 20:49:53 +01:00
parent b29989b82e
commit a6058cffb3
22 changed files with 705 additions and 614 deletions

View File

@@ -27,8 +27,6 @@ store := memcache.New()
// Initialize custom config
store := memcache.New(memcache.Config{
Servers: "localhost:11211",
Timeout: 100 * time.Millisecond,
MaxIdleConns: 10,
})
```
@@ -40,19 +38,6 @@ type Config struct {
//
// Optional. Default is "127.0.0.1:11211"
Servers string
// The socket read/write timeout.
//
// Optional. Default is 100 * time.Millisecond
Timeout time.Duration
// The maximum number of idle connections that will be maintained per address.
//
// Consider your expected traffic rates and latency carefully. This should
// be set to a number higher than your peak parallel requests.
//
// Optional. Default is 2
MaxIdleConns int
}
```
@@ -60,7 +45,5 @@ type Config struct {
```go
var ConfigDefault = Config{
Servers: "localhost:11211",
Timeout: 100 * time.Millisecond,
MaxIdleConns: 2,
}
```

View File

@@ -13,7 +13,7 @@ type Config struct {
// The socket read/write timeout.
//
// Optional. Default is 100 * time.Millisecond
Timeout time.Duration
timeout time.Duration
// The maximum number of idle connections that will be maintained per address.
//
@@ -21,14 +21,14 @@ type Config struct {
// be set to a number higher than your peak parallel requests.
//
// Optional. Default is 2
MaxIdleConns int
maxIdleConns int
}
// ConfigDefault is the default config
var ConfigDefault = Config{
Servers: "localhost:11211",
Timeout: 100 * time.Millisecond,
MaxIdleConns: 2,
timeout: 100 * time.Millisecond,
maxIdleConns: 2,
}
// Helper function to set default values
@@ -45,12 +45,12 @@ func configDefault(config ...Config) Config {
if len(cfg.Servers) < 1 {
cfg.Servers = ConfigDefault.Servers
}
if int(cfg.Timeout) == 0 {
cfg.Timeout = ConfigDefault.Timeout
}
if cfg.MaxIdleConns == 0 {
cfg.MaxIdleConns = ConfigDefault.MaxIdleConns
}
// if int(cfg.Timeout) == 0 {
// cfg.Timeout = ConfigDefault.Timeout
// }
// if cfg.MaxIdleConns == 0 {
// cfg.MaxIdleConns = ConfigDefault.MaxIdleConns
// }
return cfg
}

View File

@@ -1,6 +1,7 @@
package memcache
import (
"errors"
"strings"
"sync"
"time"
@@ -15,6 +16,9 @@ type Storage struct {
items *sync.Pool
}
// Common storage errors
var ErrNotExist = errors.New("key does not exist")
// New creates a new storage
func New(config ...Config) *Storage {
// Set default config
@@ -27,8 +31,8 @@ func New(config ...Config) *Storage {
db := mc.New(serverList...)
// Set options
db.Timeout = cfg.Timeout
db.MaxIdleConns = cfg.MaxIdleConns
db.Timeout = cfg.timeout
db.MaxIdleConns = cfg.maxIdleConns
// Ping database to ensure a connection has been made
if err := db.Ping(); err != nil {

View File

@@ -1,3 +1,105 @@
// +build memcache
package memcache
func Test_Redis_Set(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
)
err := store.Set(key, val, 0)
utils.AssertEqual(t, nil, err)
}
func Test_Redis_Get(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
)
err := store.Set(key, val, 0)
utils.AssertEqual(t, nil, err)
result, err := store.Get(key)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, val, result)
}
func Test_Redis_Set_Expiration(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
exp = 500 * time.Millisecond
)
err := store.Set(key, val, exp)
utils.AssertEqual(t, nil, err)
time.Sleep(1 * time.Second)
}
func Test_Redis_Get_Expired(t *testing.T) {
var (
store = testStore
key = "john"
)
result, err := store.Get(key)
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
func Test_Redis_Get_NotExist(t *testing.T) {
var store = testStore
result, err := store.Get("notexist")
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
func Test_Redis_Delete(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
)
err := store.Set(key, val, 0)
utils.AssertEqual(t, nil, err)
err = store.Delete(key)
utils.AssertEqual(t, nil, err)
result, err := store.Get(key)
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
func Test_Redis_Clear(t *testing.T) {
var (
store = testStore
val = []byte("doe")
)
err := store.Set("john1", val, 0)
utils.AssertEqual(t, nil, err)
err = store.Set("john2", val, 0)
utils.AssertEqual(t, nil, err)
err = store.Clear()
utils.AssertEqual(t, nil, err)
result, err := store.Get("john1")
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
result, err = store.Get("john2")
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}

View File

@@ -1,6 +1,7 @@
package memory
import (
"errors"
"sync"
"time"
)
@@ -12,6 +13,9 @@ type Storage struct {
gcInterval time.Duration
}
// Common storage errors
var ErrNotExist = errors.New("key does not exist")
type entry struct {
data []byte
expiry int64

View File

@@ -9,181 +9,104 @@ import (
"github.com/gofiber/utils"
)
func Test_Memory_Config(t *testing.T) {
t.Parallel()
store := New(Config{})
utils.AssertEqual(t, ConfigDefault.GCInterval, store.gcInterval)
}
func Test_Memory_Set(t *testing.T) {
t.Parallel()
store := New()
id := "hello"
value := []byte("Hi there!")
err := store.Set(id, value, 0)
func Test_Redis_Set(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
)
err := store.Set(key, val, 0)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, entry{value, 0}, store.db[id])
}
func Test_Memory_SetExpiry(t *testing.T) {
t.Parallel()
store := New()
id := "hello"
value := []byte("Hi there!")
expiry := time.Second * 10
err := store.Set(id, value, expiry)
func Test_Redis_Get(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
)
err := store.Set(key, val, 0)
utils.AssertEqual(t, nil, err)
now := time.Now().Unix()
fromStore, found := store.db[id]
utils.AssertEqual(t, true, found)
delta := fromStore.expiry - now
upperBound := int64(expiry.Seconds())
lowerBound := upperBound - 2
if !(delta <= upperBound && delta > lowerBound) {
t.Fatalf("Test_SetExpiry: expiry delta out of bounds (is %d, must be %d<x<=%d)", delta, lowerBound, upperBound)
}
}
func Test_Memory_GC(t *testing.T) {
t.Parallel()
store := &Storage{
db: make(map[string]entry),
gcInterval: time.Millisecond * 10,
}
id := "hello"
value := []byte("Hi there!")
expireAt := time.Now().Add(-time.Second).Unix()
store.db[id] = entry{value, expireAt}
go store.gc()
// The purpose of the long delay is to ensure the GC has time to run and delete the value
time.Sleep(time.Millisecond * 15)
store.mux.RLock()
_, found := store.db[id]
utils.AssertEqual(t, false, found)
store.mux.RUnlock()
}
func Test_Memory_Get(t *testing.T) {
t.Parallel()
store := New()
t.Run("exist", func(t *testing.T) {
id := "hello"
value := []byte("Hi there!")
store.db[id] = entry{value, 0}
returnedValue, err := store.Get(id)
result, err := store.Get(key)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, value, returnedValue)
})
t.Run("expired", func(t *testing.T) {
expired := "expired"
store.db[expired] = entry{[]byte{}, time.Now().Add(-time.Second).Unix()}
returnedValue, err := store.Get(expired)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, true, returnedValue == nil)
})
t.Run("non-exist", func(t *testing.T) {
returnedValue, err := store.Get("non-exist")
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, true, returnedValue == nil)
})
utils.AssertEqual(t, val, result)
}
func Test_Memory_Delete(t *testing.T) {
t.Parallel()
func Test_Redis_Set_Expiration(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
exp = 500 * time.Millisecond
)
store := New()
id := "hello"
value := []byte("Hi there!")
store.db[id] = entry{value, 0}
err := store.Delete(id)
err := store.Set(key, val, exp)
utils.AssertEqual(t, nil, err)
_, found := store.db[id]
utils.AssertEqual(t, false, found)
time.Sleep(1 * time.Second)
}
func Test_Memory_Clear(t *testing.T) {
t.Parallel()
func Test_Redis_Get_Expired(t *testing.T) {
var (
store = testStore
key = "john"
)
store := New()
result, err := store.Get(key)
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
id := "hello"
value := []byte("Hi there!")
func Test_Redis_Get_NotExist(t *testing.T) {
var store = testStore
store.db[id] = entry{value, 0}
result, err := store.Get("notexist")
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
err := store.Clear()
func Test_Redis_Delete(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
)
err := store.Set(key, val, 0)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, make(map[string]entry), store.db)
err = store.Delete(key)
utils.AssertEqual(t, nil, err)
result, err := store.Get(key)
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
func Benchmark_Memory_Set(b *testing.B) {
func Test_Redis_Clear(t *testing.T) {
var (
store = testStore
val = []byte("doe")
)
store := New()
err := store.Set("john1", val, 0)
utils.AssertEqual(t, nil, err)
id := "hello"
value := []byte("Hi there!")
expiry := time.Duration(0)
err = store.Set("john2", val, 0)
utils.AssertEqual(t, nil, err)
b.ReportAllocs()
b.ResetTimer()
err = store.Clear()
utils.AssertEqual(t, nil, err)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = store.Set(id, value, expiry)
}
})
}
func Benchmark_Memory_Get(b *testing.B) {
store := New()
id := "hello"
value := []byte("Hi there!")
store.db[id] = entry{value, 0}
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, _ = store.Get(id)
}
})
result, err := store.Get("john1")
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
result, err = store.Get("john2")
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}

View File

@@ -2,6 +2,7 @@ package mongodb
import (
"context"
"errors"
"sync"
"time"
@@ -18,6 +19,9 @@ type Storage struct {
items *sync.Pool
}
// Common storage errors
var ErrNotExist = errors.New("key does not exist")
type item struct {
ObjectID primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
Key string `json:"key" bson:"key"`

View File

@@ -3,12 +3,11 @@
package mongodb
import (
"context"
"github.com/gofiber/utils"
"go.mongodb.org/mongo-driver/bson"
"os"
"testing"
"time"
"github.com/gofiber/utils"
)
const (
@@ -27,141 +26,104 @@ func getConfig() Config {
}
}
func contains(arr []string, item string) bool {
for _, i := range arr {
if i == item {
return true
}
}
return false
func Test_Redis_Set(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
)
err := store.Set(key, val, 0)
utils.AssertEqual(t, nil, err)
}
func Test_MongoDB_Set_Get(t *testing.T) {
if uri == "" {
t.Skip()
}
store := New(getConfig())
defer func() {
_ = store.db.Client().Disconnect(context.TODO())
}()
func Test_Redis_Get(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
)
key := "example_key"
value := []byte("123")
err := store.Set(key, value, 0)
err := store.Set(key, val, 0)
utils.AssertEqual(t, nil, err)
getVal, getErr := store.Get(key)
utils.AssertEqual(t, nil, getErr)
utils.AssertEqual(t, value, getVal, "correctly set and get value")
result, err := store.Get(key)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, val, result)
}
func Test_MongoDB_Get_Invalid(t *testing.T) {
if uri == "" {
t.Skip()
}
store := New(getConfig())
defer func() {
_ = store.db.Client().Disconnect(context.TODO())
}()
func Test_Redis_Set_Expiration(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
exp = 500 * time.Millisecond
)
key := "random_invalid_key"
err := store.Set(key, val, exp)
utils.AssertEqual(t, nil, err)
getVal, getErr := store.Get(key)
time.Sleep(1 * time.Second)
utils.AssertEqual(t, true, getErr != nil)
utils.AssertEqual(t, true, getVal == nil, "get nil if key not found")
}
func Test_MongoDB_Set_Replace(t *testing.T) {
if uri == "" {
t.Skip()
}
store := New(getConfig())
defer func() {
_ = store.db.Client().Disconnect(context.TODO())
}()
func Test_Redis_Get_Expired(t *testing.T) {
var (
store = testStore
key = "john"
)
key := "replace_key"
value1 := []byte("value1")
value2 := []byte("value2")
setErr1 := store.Set(key, value1, 0)
setErr2 := store.Set(key, value2, 0)
val, getErr := store.Get(key)
utils.AssertEqual(t, true, setErr1 == nil)
utils.AssertEqual(t, true, setErr2 == nil)
utils.AssertEqual(t, true, getErr == nil)
utils.AssertEqual(t, value2, val, "replace value if key exists")
result, err := store.Get(key)
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
func Test_MongoDB_SetExpiry(t *testing.T) {
if uri == "" {
t.Skip()
}
store := New(getConfig())
defer func() {
_ = store.db.Client().Disconnect(context.TODO())
}()
func Test_Redis_Get_NotExist(t *testing.T) {
var store = testStore
key := "example_key_2"
value := []byte("123")
setErr := store.Set(key, value, 1*time.Nanosecond)
utils.AssertEqual(t, true, setErr == nil)
val, getErr := store.Get(key)
utils.AssertEqual(t, true, getErr == nil)
utils.AssertEqual(t, true, val == nil, "get nil if key is expire")
result, err := store.Get("notexist")
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
func Test_MongoDB_Delete(t *testing.T) {
if uri == "" {
t.Skip()
}
store := New(getConfig())
defer func() {
_ = store.db.Client().Disconnect(context.TODO())
}()
func Test_Redis_Delete(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
)
key := "example_key_3"
value := []byte("123")
err := store.Set(key, value, 0)
err := store.Set(key, val, 0)
utils.AssertEqual(t, nil, err)
err = store.Delete(key)
utils.AssertEqual(t, nil, err)
getVal, getErr := store.Get(key)
utils.AssertEqual(t, true, getErr != nil)
utils.AssertEqual(t, true, getVal == nil, "correctly delete value")
result, err := store.Get(key)
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
func Test_MongoDB_Clear(t *testing.T) {
if uri == "" {
t.Skip()
}
store := New(getConfig())
defer func() {
_ = store.db.Client().Disconnect(context.TODO())
}()
key := "example_key_4"
value := []byte("123")
err := store.Set(key, value, 10)
names, _ := store.db.ListCollectionNames(context.TODO(), bson.D{})
func Test_Redis_Clear(t *testing.T) {
var (
store = testStore
val = []byte("doe")
)
err := store.Set("john1", val, 0)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, true, contains(names, colName), "has collection")
cErr := store.Clear()
err = store.Set("john2", val, 0)
utils.AssertEqual(t, nil, err)
names2, _ := store.db.ListCollectionNames(context.TODO(), bson.D{})
utils.AssertEqual(t, nil, cErr)
utils.AssertEqual(t, false, contains(names2, colName), "do not have collection")
err = store.Clear()
utils.AssertEqual(t, nil, err)
result, err := store.Get("john1")
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
result, err = store.Get("john2")
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}

View File

@@ -1,34 +1,56 @@
# MySQL
A MySQL storage driver using `database/sql` and [`go-sql-driver/mysql`](https://github.com/go-sql-driver/mysql).
A MySQL storage driver using `database/sql` and [go-sql-driver/mysql](https://github.com/go-sql-driver/mysql).
### Creation
To create a new instance of the MySQL store, is is reccomended that you provide a database name, a table name, a username and a password. The database must exist beforehand, but the table will be created if it does not exist.
### Table of Contents
- [Signatures](#signatures)
- [Examples](#examples)
- [Config](#config)
- [Default Config](#default-config)
### Signatures
```go
// import "github.com/gomodule/redigo/redis"
func New(config ...Config) Storage
```
### Examples
Import the storage package.
```go
import "github.com/gofiber/storage/mysql"
```
You can use the following possibilities to create a storage:
```go
// Initialize default config
store := mysql.New()
// Initialize custom config
store := mysql.New(mysql.Config{
DatabaseName: "myDb",
TableName: "thisStore",
Username: "user",
Password: "yourPasswordHere",
GCInterval: 10 * time.Second,
Address: "127.0.0.1:3306",
TableName: "fiber",
DatabaseName: "fiber",
DropTable: false,
})
```
By default the store will connect to a database on `127.0.0.1:3306`. If you are using multiple MySQL stores in your application, it is strongly advised that you use different table names for each, to avoid data being overwritten or otherwise damaged.
### Config
```go
type Config struct {
// Time before deleting expired keys
//
// Default is 10 * time.Second
GCInterval time.Duration
}
```
A full list of configuration options and their defaults can be found [in `config.go`](/config.go).
### Running tests/benchmarks
Tests and benchmarks for this package require a running MySQL server, and assume you have one at `127.0.0.1:3306`. The following environment variables can be used to configure the tests:
| Name | Corresponding `Config` option |
| ---------------- | ----------------------------- |
| `MYSQL_ADDRESS` | `Address` |
| `MYSQL_USERNAME` | `Username` |
| `MYSQL_PASSWORD` | `Password` |
| `MYSQL_DATABASE` | `DatabaseName` |
If a given environment variable is not set, the default value is used.
### Default Config
```go
var ConfigDefault = Config{
GCInterval: 10 * time.Second,
Address: "127.0.0.1:3306",
TableName: "fiber",
DatabaseName: "fiber",
DropTable: false,
}
```

View File

@@ -48,7 +48,7 @@ type Config struct {
// If n < 0, no idle connections are retained.
//
// The default is 100.
MaxIdleConns int
maxIdleConns int
// The maximum number of open connections to the database.
//
@@ -59,7 +59,7 @@ type Config struct {
// If n < 0, then there is no limit on the number of open connections.
//
// The default is 100.
MaxOpenConns int
maxOpenConns int
// The maximum amount of time a connection may be reused.
//
@@ -68,7 +68,7 @@ type Config struct {
// If d < 0, connections are reused forever.
//
// The default is 1 * time.Second
ConnMaxLifetime time.Duration
connMaxLifetime time.Duration
}
// ConfigDefault is the default config
@@ -78,9 +78,9 @@ var ConfigDefault = Config{
TableName: "fiber",
DatabaseName: "fiber",
DropTable: false,
MaxOpenConns: 100,
MaxIdleConns: 100,
ConnMaxLifetime: 1 * time.Second,
maxOpenConns: 100,
maxIdleConns: 100,
connMaxLifetime: 1 * time.Second,
}
// Helper function to set default values
@@ -112,15 +112,15 @@ func configDefault(config ...Config) Config {
if cfg.DatabaseName == "" {
cfg.DatabaseName = ConfigDefault.DatabaseName
}
if cfg.MaxOpenConns == 0 {
cfg.MaxOpenConns = ConfigDefault.MaxOpenConns
}
if cfg.MaxIdleConns == 0 {
cfg.MaxIdleConns = ConfigDefault.MaxIdleConns
}
if int(cfg.ConnMaxLifetime) == 0 {
cfg.ConnMaxLifetime = ConfigDefault.ConnMaxLifetime
}
// if cfg.MaxOpenConns == 0 {
// cfg.MaxOpenConns = ConfigDefault.MaxOpenConns
// }
// if cfg.MaxIdleConns == 0 {
// cfg.MaxIdleConns = ConfigDefault.MaxIdleConns
// }
// if int(cfg.ConnMaxLifetime) == 0 {
// cfg.ConnMaxLifetime = ConfigDefault.ConnMaxLifetime
// }
return cfg
}

View File

@@ -2,6 +2,7 @@ package mysql
import (
"database/sql"
"errors"
"fmt"
"time"
@@ -21,6 +22,9 @@ type Storage struct {
sqlGC string
}
// Common storage errors
var ErrNotExist = errors.New("key does not exist")
var (
dropQuery = "DROP TABLE IF EXISTS %s;"
initQuery = []string{
@@ -41,9 +45,9 @@ func New(config ...Config) *Storage {
}
// Set options
db.SetMaxOpenConns(cfg.MaxOpenConns)
db.SetMaxIdleConns(cfg.MaxIdleConns)
db.SetConnMaxLifetime(cfg.ConnMaxLifetime)
db.SetMaxOpenConns(cfg.maxOpenConns)
db.SetMaxIdleConns(cfg.maxIdleConns)
db.SetConnMaxLifetime(cfg.connMaxLifetime)
// Ping database to ensure a connection has been made
if err := db.Ping(); err != nil {

View File

@@ -29,159 +29,104 @@ func init() {
}
}
func Test_MySQL_Set(t *testing.T) {
store := New(storeConfig)
id := "hello"
value := []byte("Hi there!")
store.Set(id, value, 0)
func Test_Redis_Set(t *testing.T) {
var (
returnedValue []byte
exp int64
store = testStore
key = "john"
val = []byte("doe")
)
store.db.QueryRow(store.sqlSelect, id).Scan(&returnedValue, &exp)
utils.AssertEqual(t, returnedValue, value)
utils.AssertEqual(t, exp, int64(0))
err := store.Set(key, val, 0)
utils.AssertEqual(t, nil, err)
}
func Test_MySQL_SetExpiry(t *testing.T) {
store := New(storeConfig)
id := "hello"
value := []byte("Hi there!")
expiry := time.Second * 10
store.Set(id, value, expiry)
now := time.Now().Unix()
func Test_Redis_Get(t *testing.T) {
var (
returnedValue []byte
exp int64
store = testStore
key = "john"
val = []byte("doe")
)
store.db.QueryRow(store.sqlSelect, id).Scan(&returnedValue, &exp)
delta := exp - now
upperBound := int64(expiry.Seconds())
lowerBound := upperBound - 2
if !(delta <= upperBound && delta > lowerBound) {
t.Fatalf("Test_SetExpiry: expiry delta out of bounds (is %d, must be %d<x<=%d)", delta, lowerBound, upperBound)
}
}
func Test_MySQL_Get(t *testing.T) {
store := New(storeConfig)
id := "hello"
value := []byte("Hi there!")
store.db.Exec(store.sqlInsert, id, utils.UnsafeString(value), 0)
returnedValue, err := store.Get(id)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, value, returnedValue)
}
func Test_MySQL_Get_NoRows(t *testing.T) {
store := New(storeConfig)
id := "hello"
returnedValue, err := store.Get(id)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, 0, len(returnedValue))
}
func Test_MySQL_Get_Expired(t *testing.T) {
store := New(storeConfig)
id := "hello"
value := []byte("Hi there!")
store.db.Exec(store.sqlInsert, id, utils.UnsafeString(value), time.Now().Add(time.Minute*-1).Unix())
returnedValue, err := store.Get(id)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, 0, len(returnedValue))
}
func Test_MySQL_Delete(t *testing.T) {
store := New(storeConfig)
id := "hello"
value := []byte("Hi there!")
store.db.Exec(store.sqlInsert, id, utils.UnsafeString(value), 0)
err := store.Delete(id)
err := store.Set(key, val, 0)
utils.AssertEqual(t, nil, err)
row := store.db.QueryRow(store.sqlSelect, id)
err = row.Scan()
utils.AssertEqual(t, noRows, err.Error())
result, err := store.Get(key)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, val, result)
}
func Test_MySQL_Clear(t *testing.T) {
func Test_Redis_Set_Expiration(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
exp = 500 * time.Millisecond
)
store := New(storeConfig)
id := "hello"
value := []byte("Hi there!")
store.db.Exec(store.sqlInsert, id, utils.UnsafeString(value), 0)
err := store.Clear()
err := store.Set(key, val, exp)
utils.AssertEqual(t, nil, err)
row := store.db.QueryRow(store.sqlSelect, id)
err = row.Scan()
utils.AssertEqual(t, noRows, err.Error())
time.Sleep(1 * time.Second)
}
func Benchmark_MySQL_Set(b *testing.B) {
store := New(storeConfig)
func Test_Redis_Get_Expired(t *testing.T) {
var (
store = testStore
key = "john"
)
key := "aaaa"
val := []byte("This is a value")
expiry := time.Second * 60
b.ResetTimer()
for n := 0; n < b.N; n++ {
store.Set(key, val, expiry)
result, err := store.Get(key)
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
func Test_Redis_Get_NotExist(t *testing.T) {
var store = testStore
result, err := store.Get("notexist")
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
func Benchmark_MySQL_Get(b *testing.B) {
store := New(storeConfig)
func Test_Redis_Delete(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
)
key := "aaaa"
val := []byte("This is a value")
err := store.Set(key, val, 0)
utils.AssertEqual(t, nil, err)
store.Set(key, val, 0)
err = store.Delete(key)
utils.AssertEqual(t, nil, err)
b.ResetTimer()
for n := 0; n < b.N; n++ {
store.Get(key)
result, err := store.Get(key)
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
func Test_Redis_Clear(t *testing.T) {
var (
store = testStore
val = []byte("doe")
)
err := store.Set("john1", val, 0)
utils.AssertEqual(t, nil, err)
err = store.Set("john2", val, 0)
utils.AssertEqual(t, nil, err)
err = store.Clear()
utils.AssertEqual(t, nil, err)
result, err := store.Get("john1")
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
result, err = store.Get("john2")
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}

View File

@@ -32,10 +32,6 @@ store := postgres.New(postgres.Config{
Database: "fiber",
TableName: "fiber",
DropTable: false,
Timeout: 30 * time.Second,
MaxOpenConns: 100,
MaxIdleConns: 100,
ConnMaxLifetime: 1 * time.Second,
})
```
@@ -82,38 +78,6 @@ type Config struct {
//
// Optional. Default is false
DropTable bool
// Maximum wait for connection, in seconds. Zero or
// n < 0 means wait indefinitely.
Timeout time.Duration
// The maximum number of connections in the idle connection pool.
//
// If MaxOpenConns is greater than 0 but less than the new MaxIdleConns,
// then the new MaxIdleConns will be reduced to match the MaxOpenConns limit.
//
// If n <= 0, no idle connections are retained.
//
// The default max idle connections is currently 2. This may change in
// a future release.
MaxIdleConns int
// The maximum number of open connections to the database.
//
// If MaxIdleConns is greater than 0 and the new MaxOpenConns is less than
// MaxIdleConns, then MaxIdleConns will be reduced to match the new
// MaxOpenConns limit.
//
// If n <= 0, then there is no limit on the number of open connections.
// The default is 0 (unlimited).
MaxOpenConns int
// The maximum amount of time a connection may be reused.
//
// Expired connections may be closed lazily before reuse.
//
// If d <= 0, connections are reused forever.
ConnMaxLifetime time.Duration
}
```
@@ -126,9 +90,5 @@ var ConfigDefault = Config{
Database: "fiber",
TableName: "fiber",
DropTable: false,
Timeout: 30 * time.Second,
MaxOpenConns: 100,
MaxIdleConns: 100,
ConnMaxLifetime: 1 * time.Second,
}
```

View File

@@ -46,7 +46,7 @@ type Config struct {
// Maximum wait for connection, in seconds. Zero or
// n < 0 means wait indefinitely.
Timeout time.Duration
timeout time.Duration
// The maximum number of connections in the idle connection pool.
//
@@ -57,7 +57,7 @@ type Config struct {
//
// The default max idle connections is currently 2. This may change in
// a future release.
MaxIdleConns int
maxIdleConns int
// The maximum number of open connections to the database.
//
@@ -67,14 +67,14 @@ type Config struct {
//
// If n <= 0, then there is no limit on the number of open connections.
// The default is 0 (unlimited).
MaxOpenConns int
maxOpenConns int
// The maximum amount of time a connection may be reused.
//
// Expired connections may be closed lazily before reuse.
//
// If d <= 0, connections are reused forever.
ConnMaxLifetime time.Duration
connMaxLifetime time.Duration
}
// ConfigDefault is the default config
@@ -85,10 +85,10 @@ var ConfigDefault = Config{
Database: "fiber",
TableName: "fiber",
DropTable: false,
Timeout: 30 * time.Second,
MaxOpenConns: 100,
MaxIdleConns: 100,
ConnMaxLifetime: 1 * time.Second,
timeout: 30 * time.Second,
maxOpenConns: 100,
maxIdleConns: 100,
connMaxLifetime: 1 * time.Second,
}
// Helper function to set default values
@@ -120,17 +120,17 @@ func configDefault(config ...Config) Config {
if cfg.TableName == "" {
cfg.TableName = ConfigDefault.TableName
}
if int(cfg.Timeout) == 0 {
cfg.Timeout = ConfigDefault.Timeout
}
if cfg.MaxOpenConns == 0 {
cfg.MaxOpenConns = ConfigDefault.MaxOpenConns
}
if cfg.MaxIdleConns == 0 {
cfg.MaxIdleConns = ConfigDefault.MaxIdleConns
}
if int(cfg.ConnMaxLifetime) == 0 {
cfg.ConnMaxLifetime = ConfigDefault.ConnMaxLifetime
}
// if int(cfg.Timeout) == 0 {
// cfg.Timeout = ConfigDefault.Timeout
// }
// if cfg.MaxOpenConns == 0 {
// cfg.MaxOpenConns = ConfigDefault.MaxOpenConns
// }
// if cfg.MaxIdleConns == 0 {
// cfg.MaxIdleConns = ConfigDefault.MaxIdleConns
// }
// if int(cfg.ConnMaxLifetime) == 0 {
// cfg.ConnMaxLifetime = ConfigDefault.ConnMaxLifetime
// }
return cfg
}

View File

@@ -23,6 +23,9 @@ type Storage struct {
sqlGC string
}
// Common storage errors
var ErrNotExist = errors.New("key does not exist")
var (
dropQuery = `DROP TABLE IF EXISTS %s;`
initQuery = []string{
@@ -47,7 +50,7 @@ func New(config ...Config) *Storage {
url.QueryEscape(cfg.Host),
cfg.Port,
url.QueryEscape(cfg.Database),
int64(cfg.Timeout.Seconds()))
int64(cfg.timeout.Seconds()))
// Create db
db, err := sql.Open("postgres", dsn)
@@ -56,9 +59,9 @@ func New(config ...Config) *Storage {
}
// Set database options
db.SetMaxOpenConns(cfg.MaxOpenConns)
db.SetMaxIdleConns(cfg.MaxIdleConns)
db.SetConnMaxLifetime(cfg.ConnMaxLifetime)
db.SetMaxOpenConns(cfg.maxOpenConns)
db.SetMaxIdleConns(cfg.maxIdleConns)
db.SetConnMaxLifetime(cfg.connMaxLifetime)
// Ping database
if err := db.Ping(); err != nil {

View File

@@ -1,3 +1,118 @@
// +build postgres
package postgres
var testStore *Storage
func init() {
testConfig := ConfigDefault
testConfig.Addr = "127.0.0.1:5432"
if v := os.Getenv("POSTGRES_ADDR"); v != "" {
testConfig.Addr = v
}
testStore = New(testConfig)
}
func Test_Redis_Set(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
)
err := store.Set(key, val, 0)
utils.AssertEqual(t, nil, err)
}
func Test_Redis_Get(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
)
err := store.Set(key, val, 0)
utils.AssertEqual(t, nil, err)
result, err := store.Get(key)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, val, result)
}
func Test_Redis_Set_Expiration(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
exp = 500 * time.Millisecond
)
err := store.Set(key, val, exp)
utils.AssertEqual(t, nil, err)
time.Sleep(1 * time.Second)
}
func Test_Redis_Get_Expired(t *testing.T) {
var (
store = testStore
key = "john"
)
result, err := store.Get(key)
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
func Test_Redis_Get_NotExist(t *testing.T) {
var store = testStore
result, err := store.Get("notexist")
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
func Test_Redis_Delete(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
)
err := store.Set(key, val, 0)
utils.AssertEqual(t, nil, err)
err = store.Delete(key)
utils.AssertEqual(t, nil, err)
result, err := store.Get(key)
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
func Test_Redis_Clear(t *testing.T) {
var (
store = testStore
val = []byte("doe")
)
err := store.Set("john1", val, 0)
utils.AssertEqual(t, nil, err)
err = store.Set("john2", val, 0)
utils.AssertEqual(t, nil, err)
err = store.Clear()
utils.AssertEqual(t, nil, err)
result, err := store.Get("john1")
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
result, err = store.Get("john2")
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}

View File

@@ -2,6 +2,7 @@ package redis
import (
"context"
"errors"
"time"
"github.com/go-redis/redis/v8"
@@ -12,6 +13,9 @@ type Storage struct {
db *redis.Client
}
// Common storage errors
var ErrNotExist = errors.New("key does not exist")
// New creates a new redis storage
func New(config ...Config) *Storage {
// Set default config
@@ -55,13 +59,10 @@ func New(config ...Config) *Storage {
// Get value by key
func (s *Storage) Get(key string) ([]byte, error) {
val, err := s.db.Get(context.Background(), key).Bytes()
if err != nil {
if err != redis.Nil {
return nil, err
if err == redis.Nil {
return nil, ErrNotExist
}
return nil, nil
}
return val, nil
return val, err
}
// Set key with value

View File

@@ -41,13 +41,12 @@ func Test_Redis_Get(t *testing.T) {
val = []byte("doe")
)
err := store.Set(key, val, 0)
utils.AssertEqual(t, nil, err)
result, err := store.Get(key)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, val, result)
result, err = store.Get("doe")
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, true, len(result) == 0)
}
func Test_Redis_Set_Expiration(t *testing.T) {
@@ -72,7 +71,15 @@ func Test_Redis_Get_Expired(t *testing.T) {
)
result, err := store.Get(key)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
func Test_Redis_Get_NotExist(t *testing.T) {
var store = testStore
result, err := store.Get("notexist")
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
@@ -90,7 +97,7 @@ func Test_Redis_Delete(t *testing.T) {
utils.AssertEqual(t, nil, err)
result, err := store.Get(key)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
@@ -110,10 +117,10 @@ func Test_Redis_Clear(t *testing.T) {
utils.AssertEqual(t, nil, err)
result, err := store.Get("john1")
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
result, err = store.Get("john2")
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}

View File

@@ -30,9 +30,6 @@ store := sqlite3.New(sqlite3.Config{
Database: "./fiber.sqlite3",
TableName: "fiber",
DropTable: false,
MaxOpenConns: 100,
MaxIdleConns: 100,
ConnMaxLifetime: 1 * time.Second,
})
```
@@ -56,36 +53,6 @@ type Config struct {
// When set to true, this will Drop any existing table with the same name
DropTable bool
// The maximum number of connections in the idle connection pool.
//
// If MaxOpenConns is greater than 0 but less than the new MaxIdleConns,
// then the new MaxIdleConns will be reduced to match the MaxOpenConns limit.
//
// If n < 0, no idle connections are retained.
//
// The default is 100.
MaxIdleConns int
// The maximum number of open connections to the database.
//
// If MaxIdleConns is greater than 0 and the new MaxOpenConns is less than
// MaxIdleConns, then MaxIdleConns will be reduced to match the new
// MaxOpenConns limit.
//
// If n < 0, then there is no limit on the number of open connections.
//
// The default is 100.
MaxOpenConns int
// The maximum amount of time a connection may be reused.
//
// Expired connections may be closed lazily before reuse.
//
// If d < 0, connections are reused forever.
//
// The default is 1 * time.Second
ConnMaxLifetime time.Duration
}
```
@@ -96,8 +63,5 @@ var ConfigDefault = Config{
Database: "./fiber.sqlite3",
TableName: "fiber",
DropTable: false,
MaxOpenConns: 100,
MaxIdleConns: 100,
ConnMaxLifetime: 1 * time.Second,
}
```

View File

@@ -30,7 +30,7 @@ type Config struct {
// If n < 0, no idle connections are retained.
//
// The default is 100.
MaxIdleConns int
maxIdleConns int
// The maximum number of open connections to the database.
//
@@ -41,7 +41,7 @@ type Config struct {
// If n < 0, then there is no limit on the number of open connections.
//
// The default is 100.
MaxOpenConns int
maxOpenConns int
// The maximum amount of time a connection may be reused.
//
@@ -50,7 +50,7 @@ type Config struct {
// If d < 0, connections are reused forever.
//
// The default is 1 * time.Second
ConnMaxLifetime time.Duration
connMaxLifetime time.Duration
}
// ConfigDefault is the default config
@@ -59,9 +59,9 @@ var ConfigDefault = Config{
Database: "./fiber.sqlite3",
TableName: "fiber",
DropTable: false,
MaxOpenConns: 100,
MaxIdleConns: 100,
ConnMaxLifetime: 1 * time.Second,
maxOpenConns: 100,
maxIdleConns: 100,
connMaxLifetime: 1 * time.Second,
}
// Helper function to set default values
@@ -84,14 +84,14 @@ func configDefault(config ...Config) Config {
if cfg.TableName == "" {
cfg.TableName = ConfigDefault.TableName
}
if cfg.MaxOpenConns == 0 {
cfg.MaxOpenConns = ConfigDefault.MaxOpenConns
}
if cfg.MaxIdleConns == 0 {
cfg.MaxIdleConns = ConfigDefault.MaxIdleConns
}
if int(cfg.ConnMaxLifetime) == 0 {
cfg.ConnMaxLifetime = ConfigDefault.ConnMaxLifetime
}
// if cfg.MaxOpenConns == 0 {
// cfg.MaxOpenConns = ConfigDefault.MaxOpenConns
// }
// if cfg.MaxIdleConns == 0 {
// cfg.MaxIdleConns = ConfigDefault.MaxIdleConns
// }
// if int(cfg.ConnMaxLifetime) == 0 {
// cfg.ConnMaxLifetime = ConfigDefault.ConnMaxLifetime
// }
return cfg
}

View File

@@ -2,6 +2,7 @@ package sqlite3
import (
"database/sql"
"errors"
"fmt"
"time"
@@ -22,6 +23,9 @@ type Storage struct {
sqlGC string
}
// Common storage errors
var ErrNotExist = errors.New("key does not exist")
var (
dropQuery = `DROP TABLE IF EXISTS %s;`
initQuery = []string{
@@ -46,9 +50,9 @@ func New(config ...Config) *Storage {
}
// Set database options
db.SetMaxOpenConns(cfg.MaxOpenConns)
db.SetMaxIdleConns(cfg.MaxIdleConns)
db.SetConnMaxLifetime(cfg.ConnMaxLifetime)
db.SetMaxOpenConns(cfg.maxOpenConns)
db.SetMaxIdleConns(cfg.maxIdleConns)
db.SetConnMaxLifetime(cfg.connMaxLifetime)
// Ping database
if err := db.Ping(); err != nil {

View File

@@ -1,3 +1,5 @@
// +build sqlite3
package sqlite3
import (
@@ -8,31 +10,113 @@ import (
_ "github.com/mattn/go-sqlite3"
)
func Test_New(t *testing.T) {
New()
var testStore *Storage
func init() {
testConfig := ConfigDefault
testConfig.Database = ":memory:"
testStore = New(testConfig)
}
func Test_Sqlite_Get_Set(t *testing.T) {
s := New(Config{
Database: ":memory:",
})
err := s.Set("fiber-10k-stars?", []byte("yes!"), time.Duration(time.Hour*1))
utils.AssertEqual(t, nil, err)
func Test_Redis_Set(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
)
b, err := s.Get("fiber-10k-stars?")
err := store.Set(key, val, 0)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, []byte("yes!"), b)
}
func Test_Sqlite_Expiration(t *testing.T) {
s := New(Config{
Database: ":memory:",
})
func Test_Redis_Get(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
)
err := s.Set("fiber-20k-stars?", []byte("yes!"), time.Duration(time.Nanosecond/2))
err := store.Set(key, val, 0)
utils.AssertEqual(t, nil, err)
b, err := s.Get("fiber-220k-stars?")
result, err := store.Get(key)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, true, b == nil)
utils.AssertEqual(t, val, result)
}
func Test_Redis_Set_Expiration(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
exp = 500 * time.Millisecond
)
err := store.Set(key, val, exp)
utils.AssertEqual(t, nil, err)
time.Sleep(1 * time.Second)
}
func Test_Redis_Get_Expired(t *testing.T) {
var (
store = testStore
key = "john"
)
result, err := store.Get(key)
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
func Test_Redis_Get_NotExist(t *testing.T) {
var store = testStore
result, err := store.Get("notexist")
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
func Test_Redis_Delete(t *testing.T) {
var (
store = testStore
key = "john"
val = []byte("doe")
)
err := store.Set(key, val, 0)
utils.AssertEqual(t, nil, err)
err = store.Delete(key)
utils.AssertEqual(t, nil, err)
result, err := store.Get(key)
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}
func Test_Redis_Clear(t *testing.T) {
var (
store = testStore
val = []byte("doe")
)
err := store.Set("john1", val, 0)
utils.AssertEqual(t, nil, err)
err = store.Set("john2", val, 0)
utils.AssertEqual(t, nil, err)
err = store.Clear()
utils.AssertEqual(t, nil, err)
result, err := store.Get("john1")
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
result, err = store.Get("john2")
utils.AssertEqual(t, ErrNotExist, err)
utils.AssertEqual(t, true, len(result) == 0)
}