mirror of
https://github.com/gofiber/storage.git
synced 2025-10-04 08:16:36 +08:00
6
.github/README.md
vendored
6
.github/README.md
vendored
@@ -10,10 +10,10 @@ Premade storage drivers that implement [`fiber.Storage`](https://github.com/gofi
|
|||||||
* [Memcached](/memcached) **(UNFINISHED)**
|
* [Memcached](/memcached) **(UNFINISHED)**
|
||||||
* [Memory](/memory)
|
* [Memory](/memory)
|
||||||
* [MongoDB](/mongodb)
|
* [MongoDB](/mongodb)
|
||||||
* [MySQL](/mysql) **(UNFINISHED)**
|
* [MySQL](/mysql) **(UNFINISHED)**
|
||||||
* [Postgres](/postgres) **(UNFINISHED)**
|
* [Postgres](/postgres)
|
||||||
* [Redis](/redis)
|
* [Redis](/redis)
|
||||||
* [SQLite3](/sqlite3) **(UNFINISHED)**
|
* [SQLite3](/sqlite3)
|
||||||
|
|
||||||
## 🤔 Something missing?
|
## 🤔 Something missing?
|
||||||
|
|
||||||
|
1
go.mod
1
go.mod
@@ -5,6 +5,7 @@ go 1.14
|
|||||||
require (
|
require (
|
||||||
github.com/go-redis/redis/v8 v8.3.3
|
github.com/go-redis/redis/v8 v8.3.3
|
||||||
github.com/gofiber/utils v0.1.0
|
github.com/gofiber/utils v0.1.0
|
||||||
|
github.com/lib/pq v1.8.0
|
||||||
github.com/mattn/go-sqlite3 v1.14.4
|
github.com/mattn/go-sqlite3 v1.14.4
|
||||||
go.mongodb.org/mongo-driver v1.4.2
|
go.mongodb.org/mongo-driver v1.4.2
|
||||||
)
|
)
|
||||||
|
2
go.sum
2
go.sum
@@ -78,6 +78,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
|||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
|
||||||
|
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
|
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
|
||||||
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
||||||
github.com/mattn/go-sqlite3 v1.14.4 h1:4rQjbDxdu9fSgI/r3KN72G3c2goxknAqHHgPWWs8UlI=
|
github.com/mattn/go-sqlite3 v1.14.4 h1:4rQjbDxdu9fSgI/r3KN72G3c2goxknAqHHgPWWs8UlI=
|
||||||
|
@@ -4,12 +4,91 @@ import "time"
|
|||||||
|
|
||||||
// Config defines the config for storage.
|
// Config defines the config for storage.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
// Time before deleting expired keys
|
||||||
|
//
|
||||||
|
// Default is 10 * time.Second
|
||||||
GCInterval time.Duration
|
GCInterval time.Duration
|
||||||
|
|
||||||
|
// DB host
|
||||||
|
//
|
||||||
|
// Optional. Default is "127.0.0.1"
|
||||||
|
Host string
|
||||||
|
|
||||||
|
// DB port
|
||||||
|
//
|
||||||
|
// Optional. Default is "5432"
|
||||||
|
Port int64
|
||||||
|
|
||||||
|
// DB user name
|
||||||
|
//
|
||||||
|
// Optional. Default is ""
|
||||||
|
Username string
|
||||||
|
|
||||||
|
// DB user password
|
||||||
|
//
|
||||||
|
// Optional. Default is ""
|
||||||
|
Password string
|
||||||
|
|
||||||
|
// DB name
|
||||||
|
//
|
||||||
|
// Optional. Default is "fiber"
|
||||||
|
Database string
|
||||||
|
|
||||||
|
// DB table name
|
||||||
|
//
|
||||||
|
// Optional. Default is "fiber"
|
||||||
|
TableName string
|
||||||
|
|
||||||
|
// Drop any existing table with the same name
|
||||||
|
//
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigDefault is the default config
|
// ConfigDefault is the default config
|
||||||
var ConfigDefault = Config{
|
var ConfigDefault = Config{
|
||||||
GCInterval: 10 * time.Second,
|
GCInterval: 10 * time.Second,
|
||||||
|
Host: "127.0.0.1",
|
||||||
|
Port: 5432,
|
||||||
|
Database: "fiber",
|
||||||
|
TableName: "fiber",
|
||||||
|
DropTable: false,
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
MaxOpenConns: 100,
|
||||||
|
MaxIdleConns: 100,
|
||||||
|
ConnMaxLifetime: 1 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to set default values
|
// Helper function to set default values
|
||||||
@@ -26,5 +105,32 @@ func configDefault(config ...Config) Config {
|
|||||||
if int(cfg.GCInterval) == 0 {
|
if int(cfg.GCInterval) == 0 {
|
||||||
cfg.GCInterval = ConfigDefault.GCInterval
|
cfg.GCInterval = ConfigDefault.GCInterval
|
||||||
}
|
}
|
||||||
|
if cfg.Host == "" {
|
||||||
|
cfg.Host = ConfigDefault.Host
|
||||||
|
}
|
||||||
|
if cfg.Port == 0 {
|
||||||
|
cfg.Port = ConfigDefault.Port
|
||||||
|
}
|
||||||
|
if cfg.Host == "" {
|
||||||
|
cfg.Host = ConfigDefault.Host
|
||||||
|
}
|
||||||
|
if cfg.Database == "" {
|
||||||
|
cfg.Database = ConfigDefault.Database
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
@@ -1,22 +1,96 @@
|
|||||||
package postgres
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/utils"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Storage interface that is implemented by storage providers
|
// Storage interface that is implemented by storage providers
|
||||||
type Storage struct {
|
type Storage struct {
|
||||||
|
db *sql.DB
|
||||||
gcInterval time.Duration
|
gcInterval time.Duration
|
||||||
|
|
||||||
|
sqlSelect string
|
||||||
|
sqlInsert string
|
||||||
|
sqlDelete string
|
||||||
|
sqlClear string
|
||||||
|
sqlGC string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
dropQuery = `DROP TABLE IF EXISTS %s;`
|
||||||
|
initQuery = []string{
|
||||||
|
`CREATE TABLE IF NOT EXISTS %s (
|
||||||
|
key VARCHAR(64) PRIMARY KEY NOT NULL DEFAULT '',
|
||||||
|
data TEXT NOT NULL,
|
||||||
|
exp BIGINT NOT NULL DEFAULT '0'
|
||||||
|
);`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS exp ON %s (exp);`,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// New creates a new storage
|
// New creates a new storage
|
||||||
func New(config ...Config) *Storage {
|
func New(config ...Config) *Storage {
|
||||||
// Set default config
|
// Set default config
|
||||||
cfg := configDefault(config...)
|
cfg := configDefault(config...)
|
||||||
|
|
||||||
|
// Create data source name
|
||||||
|
dsn := fmt.Sprintf("postgresql://%s:%s@%s:%d/%s?connect_timeout=%d&sslmode=disable",
|
||||||
|
url.QueryEscape(cfg.Username),
|
||||||
|
cfg.Password,
|
||||||
|
url.QueryEscape(cfg.Host),
|
||||||
|
cfg.Port,
|
||||||
|
url.QueryEscape(cfg.Database),
|
||||||
|
int64(cfg.Timeout.Seconds()))
|
||||||
|
|
||||||
|
// Create db
|
||||||
|
db, err := sql.Open("postgres", dsn)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set database options
|
||||||
|
db.SetMaxOpenConns(cfg.MaxOpenConns)
|
||||||
|
db.SetMaxIdleConns(cfg.MaxIdleConns)
|
||||||
|
db.SetConnMaxLifetime(cfg.ConnMaxLifetime)
|
||||||
|
|
||||||
|
// Ping database
|
||||||
|
if err := db.Ping(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop table if set to true
|
||||||
|
if cfg.DropTable {
|
||||||
|
if _, err = db.Exec(fmt.Sprintf(dropQuery, cfg.TableName)); err != nil {
|
||||||
|
_ = db.Close()
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init database queries
|
||||||
|
for _, query := range initQuery {
|
||||||
|
if _, err := db.Exec(fmt.Sprintf(query, cfg.TableName)); err != nil {
|
||||||
|
_ = db.Close()
|
||||||
|
fmt.Println(fmt.Sprintf(query, cfg.TableName))
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create storage
|
// Create storage
|
||||||
store := &Storage{
|
store := &Storage{
|
||||||
|
db: db,
|
||||||
gcInterval: cfg.GCInterval,
|
gcInterval: cfg.GCInterval,
|
||||||
|
sqlSelect: fmt.Sprintf(`SELECT data, exp FROM %s WHERE key=$1;`, cfg.TableName),
|
||||||
|
sqlInsert: fmt.Sprintf("INSERT INTO %s (key, data, exp) VALUES ($1, $2, $3)", cfg.TableName),
|
||||||
|
sqlDelete: fmt.Sprintf("DELETE FROM %s WHERE key=$1", cfg.TableName),
|
||||||
|
sqlClear: fmt.Sprintf("DELETE FROM %s;", cfg.TableName),
|
||||||
|
sqlGC: fmt.Sprintf("DELETE FROM %s WHERE exp <= $1", cfg.TableName),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start garbage collector
|
// Start garbage collector
|
||||||
@@ -25,31 +99,57 @@ func New(config ...Config) *Storage {
|
|||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var noRows = errors.New("sql: no rows in result set")
|
||||||
|
|
||||||
// Get value by key
|
// Get value by key
|
||||||
func (s *Storage) Get(key string) ([]byte, error) {
|
func (s *Storage) Get(key string) ([]byte, error) {
|
||||||
return nil, nil
|
row := s.db.QueryRow(s.sqlSelect, key)
|
||||||
|
|
||||||
|
// Add db response to data
|
||||||
|
var (
|
||||||
|
data = []byte{}
|
||||||
|
exp int64 = 0
|
||||||
|
)
|
||||||
|
if err := row.Scan(&data, &exp); err != nil {
|
||||||
|
if err != noRows {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the expiration time has already passed, then return nil
|
||||||
|
if time.Now().After(time.Unix(exp, 0)) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set key with value
|
// Set key with value
|
||||||
func (s *Storage) Set(key string, val []byte, exp time.Duration) error {
|
func (s *Storage) Set(key string, val []byte, exp time.Duration) error {
|
||||||
return nil
|
_, err := s.db.Exec(s.sqlInsert, key, utils.UnsafeString(val), time.Now().Add(exp).Unix())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete key by key
|
// Delete entry by key
|
||||||
func (s *Storage) Delete(key string) error {
|
func (s *Storage) Delete(key string) error {
|
||||||
return nil
|
_, err := s.db.Exec(s.sqlDelete, key)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear all keys
|
// Clear all entries, including unexpired
|
||||||
func (s *Storage) Clear() error {
|
func (s *Storage) Clear() error {
|
||||||
return nil
|
_, err := s.db.Exec(s.sqlClear)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Garbage collector to delete expired keys
|
// GC deletes all expired entries
|
||||||
func (s *Storage) gc() {
|
func (s *Storage) gc() {
|
||||||
tick := time.NewTicker(s.gcInterval)
|
tick := time.NewTicker(s.gcInterval)
|
||||||
for {
|
for {
|
||||||
<-tick.C
|
<-tick.C
|
||||||
// clean entries
|
if _, err := s.db.Exec(s.sqlGC); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,7 @@ type Config struct {
|
|||||||
// The network type, either tcp or unix.
|
// The network type, either tcp or unix.
|
||||||
// Default is tcp.
|
// Default is tcp.
|
||||||
Network string
|
Network string
|
||||||
|
|
||||||
// host:port address.
|
// host:port address.
|
||||||
Addr string
|
Addr string
|
||||||
|
|
||||||
|
@@ -12,7 +12,7 @@ type Config struct {
|
|||||||
// DB file path
|
// DB file path
|
||||||
//
|
//
|
||||||
// Default is "./fiber.sqlite3"
|
// Default is "./fiber.sqlite3"
|
||||||
FilePath string
|
Database string
|
||||||
|
|
||||||
// DB table name
|
// DB table name
|
||||||
//
|
//
|
||||||
@@ -56,7 +56,7 @@ type Config struct {
|
|||||||
// ConfigDefault is the default config
|
// ConfigDefault is the default config
|
||||||
var ConfigDefault = Config{
|
var ConfigDefault = Config{
|
||||||
GCInterval: 10 * time.Second,
|
GCInterval: 10 * time.Second,
|
||||||
FilePath: "./fiber.sqlite3",
|
Database: "./fiber.sqlite3",
|
||||||
TableName: "fiber",
|
TableName: "fiber",
|
||||||
DropTable: false,
|
DropTable: false,
|
||||||
MaxOpenConns: 100,
|
MaxOpenConns: 100,
|
||||||
@@ -78,8 +78,8 @@ func configDefault(config ...Config) Config {
|
|||||||
if int(cfg.GCInterval) == 0 {
|
if int(cfg.GCInterval) == 0 {
|
||||||
cfg.GCInterval = ConfigDefault.GCInterval
|
cfg.GCInterval = ConfigDefault.GCInterval
|
||||||
}
|
}
|
||||||
if cfg.FilePath == "" {
|
if cfg.Database == "" {
|
||||||
cfg.FilePath = ConfigDefault.FilePath
|
cfg.Database = ConfigDefault.Database
|
||||||
}
|
}
|
||||||
if cfg.TableName == "" {
|
if cfg.TableName == "" {
|
||||||
cfg.TableName = ConfigDefault.TableName
|
cfg.TableName = ConfigDefault.TableName
|
||||||
|
@@ -41,7 +41,7 @@ func New(config ...Config) *Storage {
|
|||||||
cfg := configDefault(config...)
|
cfg := configDefault(config...)
|
||||||
|
|
||||||
// Create db
|
// Create db
|
||||||
db, err := sql.Open("sqlite3", cfg.FilePath)
|
db, err := sql.Open("sqlite3", cfg.Database)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -118,7 +118,7 @@ func (s *Storage) Get(key string) ([]byte, error) {
|
|||||||
|
|
||||||
// Set key with value
|
// Set key with value
|
||||||
func (s *Storage) Set(key string, val []byte, exp time.Duration) error {
|
func (s *Storage) Set(key string, val []byte, exp time.Duration) error {
|
||||||
_, err := s.db.Exec(s.sqlInsert, key, utils.GetString(val), time.Now().Add(exp).Unix())
|
_, err := s.db.Exec(s.sqlInsert, key, utils.UnsafeString(val), time.Now().Add(exp).Unix())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@ func Test_New(t *testing.T) {
|
|||||||
|
|
||||||
func Test_Get_Set(t *testing.T) {
|
func Test_Get_Set(t *testing.T) {
|
||||||
s := New(Config{
|
s := New(Config{
|
||||||
FilePath: ":memory:",
|
Database: ":memory:",
|
||||||
})
|
})
|
||||||
err := s.Set("fiber-10k-stars?", []byte("yes!"), time.Duration(time.Hour*1))
|
err := s.Set("fiber-10k-stars?", []byte("yes!"), time.Duration(time.Hour*1))
|
||||||
utils.AssertEqual(t, nil, err)
|
utils.AssertEqual(t, nil, err)
|
||||||
@@ -26,7 +26,7 @@ func Test_Get_Set(t *testing.T) {
|
|||||||
|
|
||||||
func Test_Expiration(t *testing.T) {
|
func Test_Expiration(t *testing.T) {
|
||||||
s := New(Config{
|
s := New(Config{
|
||||||
FilePath: ":memory:",
|
Database: ":memory:",
|
||||||
})
|
})
|
||||||
|
|
||||||
err := s.Set("fiber-20k-stars?", []byte("yes!"), time.Duration(time.Nanosecond/2))
|
err := s.Set("fiber-20k-stars?", []byte("yes!"), time.Duration(time.Nanosecond/2))
|
||||||
|
Reference in New Issue
Block a user