mirror of
https://github.com/gofiber/storage.git
synced 2025-10-04 00:06:35 +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)**
|
||||
* [Memory](/memory)
|
||||
* [MongoDB](/mongodb)
|
||||
* [MySQL](/mysql) **(UNFINISHED)**
|
||||
* [Postgres](/postgres) **(UNFINISHED)**
|
||||
* [MySQL](/mysql) **(UNFINISHED)**
|
||||
* [Postgres](/postgres)
|
||||
* [Redis](/redis)
|
||||
* [SQLite3](/sqlite3) **(UNFINISHED)**
|
||||
* [SQLite3](/sqlite3)
|
||||
|
||||
## 🤔 Something missing?
|
||||
|
||||
|
1
go.mod
1
go.mod
@@ -5,6 +5,7 @@ go 1.14
|
||||
require (
|
||||
github.com/go-redis/redis/v8 v8.3.3
|
||||
github.com/gofiber/utils v0.1.0
|
||||
github.com/lib/pq v1.8.0
|
||||
github.com/mattn/go-sqlite3 v1.14.4
|
||||
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/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
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/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
||||
github.com/mattn/go-sqlite3 v1.14.4 h1:4rQjbDxdu9fSgI/r3KN72G3c2goxknAqHHgPWWs8UlI=
|
||||
|
@@ -4,12 +4,91 @@ import "time"
|
||||
|
||||
// Config defines the config for storage.
|
||||
type Config struct {
|
||||
// Time before deleting expired keys
|
||||
//
|
||||
// Default is 10 * time.Second
|
||||
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
|
||||
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
|
||||
@@ -26,5 +105,32 @@ func configDefault(config ...Config) Config {
|
||||
if int(cfg.GCInterval) == 0 {
|
||||
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
|
||||
}
|
||||
|
@@ -1,22 +1,96 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/utils"
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
// Storage interface that is implemented by storage providers
|
||||
type Storage struct {
|
||||
db *sql.DB
|
||||
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
|
||||
func New(config ...Config) *Storage {
|
||||
// Set default 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
|
||||
store := &Storage{
|
||||
db: db,
|
||||
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
|
||||
@@ -25,31 +99,57 @@ func New(config ...Config) *Storage {
|
||||
return store
|
||||
}
|
||||
|
||||
var noRows = errors.New("sql: no rows in result set")
|
||||
|
||||
// Get value by key
|
||||
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
|
||||
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 {
|
||||
return nil
|
||||
_, err := s.db.Exec(s.sqlDelete, key)
|
||||
return err
|
||||
}
|
||||
|
||||
// Clear all keys
|
||||
// Clear all entries, including unexpired
|
||||
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() {
|
||||
tick := time.NewTicker(s.gcInterval)
|
||||
for {
|
||||
<-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.
|
||||
// Default is tcp.
|
||||
Network string
|
||||
|
||||
// host:port address.
|
||||
Addr string
|
||||
|
||||
|
@@ -12,7 +12,7 @@ type Config struct {
|
||||
// DB file path
|
||||
//
|
||||
// Default is "./fiber.sqlite3"
|
||||
FilePath string
|
||||
Database string
|
||||
|
||||
// DB table name
|
||||
//
|
||||
@@ -56,7 +56,7 @@ type Config struct {
|
||||
// ConfigDefault is the default config
|
||||
var ConfigDefault = Config{
|
||||
GCInterval: 10 * time.Second,
|
||||
FilePath: "./fiber.sqlite3",
|
||||
Database: "./fiber.sqlite3",
|
||||
TableName: "fiber",
|
||||
DropTable: false,
|
||||
MaxOpenConns: 100,
|
||||
@@ -78,8 +78,8 @@ func configDefault(config ...Config) Config {
|
||||
if int(cfg.GCInterval) == 0 {
|
||||
cfg.GCInterval = ConfigDefault.GCInterval
|
||||
}
|
||||
if cfg.FilePath == "" {
|
||||
cfg.FilePath = ConfigDefault.FilePath
|
||||
if cfg.Database == "" {
|
||||
cfg.Database = ConfigDefault.Database
|
||||
}
|
||||
if cfg.TableName == "" {
|
||||
cfg.TableName = ConfigDefault.TableName
|
||||
|
@@ -41,7 +41,7 @@ func New(config ...Config) *Storage {
|
||||
cfg := configDefault(config...)
|
||||
|
||||
// Create db
|
||||
db, err := sql.Open("sqlite3", cfg.FilePath)
|
||||
db, err := sql.Open("sqlite3", cfg.Database)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func (s *Storage) Get(key string) ([]byte, error) {
|
||||
|
||||
// Set key with value
|
||||
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
|
||||
}
|
||||
|
||||
|
@@ -14,7 +14,7 @@ func Test_New(t *testing.T) {
|
||||
|
||||
func Test_Get_Set(t *testing.T) {
|
||||
s := New(Config{
|
||||
FilePath: ":memory:",
|
||||
Database: ":memory:",
|
||||
})
|
||||
err := s.Set("fiber-10k-stars?", []byte("yes!"), time.Duration(time.Hour*1))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
@@ -26,7 +26,7 @@ func Test_Get_Set(t *testing.T) {
|
||||
|
||||
func Test_Expiration(t *testing.T) {
|
||||
s := New(Config{
|
||||
FilePath: ":memory:",
|
||||
Database: ":memory:",
|
||||
})
|
||||
|
||||
err := s.Set("fiber-20k-stars?", []byte("yes!"), time.Duration(time.Nanosecond/2))
|
||||
|
Reference in New Issue
Block a user