Merge pull request #153 from Technerder/main

Updated Postgre module to use pgx instead of pq
This commit is contained in:
RW
2023-03-12 18:58:37 +01:00
committed by GitHub
7 changed files with 153 additions and 155 deletions

View File

@@ -27,9 +27,8 @@ jobs:
strategy:
matrix:
go-version:
- 1.16.x
- 1.18.x
- 1.19.x
- 1.20.x
platform:
- ubuntu-latest
- windows-latest

View File

@@ -1,6 +1,6 @@
# Postgres
A Postgres storage driver using [lib/pq](https://github.com/lib/pq).
A Postgres storage driver using [jackc/pgx](https://github.com/jackc/pgx).
### Table of Contents
- [Signatures](#signatures)
@@ -17,7 +17,7 @@ func (s *Storage) Set(key string, val []byte, exp time.Duration) error
func (s *Storage) Delete(key string) error
func (s *Storage) Reset() error
func (s *Storage) Close() error
func (s *Storage) Conn() *sql.DB
func (s *Storage) Conn() *pgxpool.Pool
```
### Installation
Postgres is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet:
@@ -26,13 +26,13 @@ go mod init github.com/<user>/<repo>
```
And then install the postgres implementation:
```bash
go get github.com/gofiber/storage/postgres
go get github.com/gofiber/storage/postgres/v2
```
### Examples
Import the storage package.
```go
import "github.com/gofiber/storage/postgres"
import "github.com/gofiber/storage/postgres/v2"
```
You can use the following possibilities to create a storage:
@@ -42,20 +42,10 @@ store := postgres.New()
// Initialize custom config
store := postgres.New(postgres.Config{
Host: "127.0.0.1",
Port: 5432,
Database: "fiber",
Db: dbPool,
Table: "fiber_storage",
Reset: false,
GCInterval: 10 * time.Second,
SslMode: "disable",
})
// Initialize custom config using connection string
store := postgres.New(postgres.Config{
ConnectionURI: "postgresql://user:password@localhost:5432/fiber"
Reset: false,
GCInterval: 10 * time.Second,
})
```
@@ -63,6 +53,11 @@ store := postgres.New(postgres.Config{
```go
// Config defines the config for storage.
type Config struct {
// DB pgxpool.Pool object will override connection uri and other connection fields
//
// Optional. Default is nil
DB *pgxpool.Pool
// Connection string to use for DB. Will override all other authentication values if used
//
// Optional. Default is ""
@@ -98,6 +93,11 @@ type Config struct {
// Optional. Default is "fiber_storage"
Table string
// The SSL mode for the connection
//
// Optional. Default is "disable"
SSLMode string
// Reset clears any existing keys in existing Table
//
// Optional. Default is false
@@ -107,24 +107,20 @@ type Config struct {
//
// Optional. Default is 10 * time.Second
GCInterval time.Duration
// The SSL mode for the connection
//
// Optional. Default is "disable"
SslMode string
}
```
### Default Config
```go
// ConfigDefault is the default config
var ConfigDefault = Config{
ConnectionURI: "",
Host: "127.0.0.1",
Port: 5432,
Database: "fiber",
Table: "fiber_storage",
Reset: false,
GCInterval: 10 * time.Second,
SslMode: "disable",
ConnectionURI: "",
Host: "127.0.0.1",
Port: 5432,
Database: "fiber",
Table: "fiber_storage",
SSLMode: "disable",
Reset: false,
GCInterval: 10 * time.Second,
}
```

View File

@@ -1,11 +1,21 @@
package postgres
import (
"fmt"
"net/url"
"strings"
"time"
"github.com/jackc/pgx/v5/pgxpool"
)
// Config defines the config for storage.
type Config struct {
// DB pgxpool.Pool object will override connection uri and other connection fields
//
// Optional. Default is nil
DB *pgxpool.Pool
// Connection string to use for DB. Will override all other authentication values if used
//
// Optional. Default is ""
@@ -44,7 +54,7 @@ type Config struct {
// The SSL mode for the connection
//
// Optional. Default is "disable"
SslMode string
SSLMode string
// Reset clears any existing keys in existing Table
//
@@ -55,57 +65,50 @@ type Config struct {
//
// Optional. Default is 10 * time.Second
GCInterval time.Duration
////////////////////////////////////
// Adaptor related config options //
////////////////////////////////////
// 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{
ConnectionURI: "",
Host: "127.0.0.1",
Port: 5432,
Database: "fiber",
Table: "fiber_storage",
SslMode: "disable",
Reset: false,
GCInterval: 10 * time.Second,
maxOpenConns: 100,
maxIdleConns: 100,
connMaxLifetime: 1 * time.Second,
ConnectionURI: "",
Host: "127.0.0.1",
Port: 5432,
Database: "fiber",
Table: "fiber_storage",
SSLMode: "disable",
Reset: false,
GCInterval: 10 * time.Second,
}
func (c *Config) getDSN() string {
// Just return ConnectionURI if it's already exists
if c.ConnectionURI != "" {
return c.ConnectionURI
}
// Generate DSN
dsn := "postgresql://"
if c.Username != "" {
dsn += url.QueryEscape(c.Username)
}
if c.Password != "" {
dsn += ":" + url.QueryEscape(c.Password)
}
if c.Username != "" || c.Password != "" {
dsn += "@"
}
// unix socket host path
if strings.HasPrefix(c.Host, "/") {
dsn += fmt.Sprintf("%s:%d", c.Host, c.Port)
} else {
dsn += fmt.Sprintf("%s:%d", url.QueryEscape(c.Host), c.Port)
}
dsn += fmt.Sprintf("/%s?sslmode=%s",
url.QueryEscape(c.Database),
c.SSLMode)
return dsn
}
// Helper function to set default values
@@ -114,7 +117,6 @@ func configDefault(config ...Config) Config {
if len(config) < 1 {
return ConfigDefault
}
// Override default config
cfg := config[0]
@@ -131,8 +133,8 @@ func configDefault(config ...Config) Config {
if cfg.Table == "" {
cfg.Table = ConfigDefault.Table
}
if cfg.SslMode == "" {
cfg.SslMode = ConfigDefault.SslMode
if cfg.Table == "" {
cfg.Table = ConfigDefault.Table
}
if int(cfg.GCInterval.Seconds()) <= 0 {
cfg.GCInterval = ConfigDefault.GCInterval

View File

@@ -1,8 +1,17 @@
module github.com/gofiber/storage/postgres
module github.com/gofiber/storage/postgres/v2
go 1.16
go 1.19
require (
github.com/gofiber/utils v1.0.1
github.com/lib/pq v1.10.7
github.com/jackc/pgx/v5 v5.3.1
)
require (
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.0 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/text v0.8.0 // indirect
)

View File

@@ -1,4 +1,27 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/gofiber/utils v1.0.1 h1:knct4cXwBipWQqFrOy1Pv6UcgPM+EXo9jDgc66V1Qio=
github.com/gofiber/utils v1.0.1/go.mod h1:pacRFtghAE3UoknMOUiXh2Io/nLWSUHtQCi/3QASsOc=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
github.com/jackc/puddle/v2 v2.2.0 h1:RdcDk92EJBuBS55nQMMYFXTxwstHug4jkhT5pq8VxPk=
github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -1,19 +1,20 @@
package postgres
import (
"database/sql"
"context"
"errors"
"fmt"
"net/url"
"os"
"strings"
"time"
_ "github.com/lib/pq"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
// Storage interface that is implemented by storage providers
type Storage struct {
db *sql.DB
db *pgxpool.Pool
gcInterval time.Duration
done chan struct{}
@@ -45,62 +46,33 @@ func New(config ...Config) *Storage {
// Set default config
cfg := configDefault(config...)
// Create data source name
var dsn string
if cfg.ConnectionURI != "" {
dsn = cfg.ConnectionURI
} else {
dsn = "postgresql://"
if cfg.Username != "" {
dsn += url.QueryEscape(cfg.Username)
// Select db connection
var err error
db := cfg.DB
if db == nil {
db, err = pgxpool.New(context.Background(), cfg.getDSN())
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to create connection pool: %v\n", err)
}
if cfg.Password != "" {
dsn += ":" + url.QueryEscape(cfg.Password)
}
if cfg.Username != "" || cfg.Password != "" {
dsn += "@"
}
// unix socket host path
if strings.HasPrefix(cfg.Host, "/") {
dsn += fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
} else {
dsn += fmt.Sprintf("%s:%d", url.QueryEscape(cfg.Host), cfg.Port)
}
dsn += fmt.Sprintf("/%s?connect_timeout=%d&sslmode=%s",
url.QueryEscape(cfg.Database),
int64(cfg.timeout.Seconds()),
cfg.SslMode)
}
// 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 {
if err := db.Ping(context.Background()); err != nil {
panic(err)
}
// Drop table if set to true
if cfg.Reset {
if _, err = db.Exec(fmt.Sprintf(dropQuery, cfg.Table)); err != nil {
_ = db.Close()
if _, err := db.Exec(context.Background(), fmt.Sprintf(dropQuery, cfg.Table)); err != nil {
db.Close()
panic(err)
}
}
// Init database queries
for _, query := range initQuery {
if _, err := db.Exec(fmt.Sprintf(query, cfg.Table)); err != nil {
_ = db.Close()
if _, err := db.Exec(context.Background(), fmt.Sprintf(query, cfg.Table)); err != nil {
db.Close()
panic(err)
}
}
@@ -125,21 +97,19 @@ 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) {
if len(key) <= 0 {
return nil, nil
}
row := s.db.QueryRow(s.sqlSelect, key)
row := s.db.QueryRow(context.Background(), s.sqlSelect, key)
// Add db response to data
var (
data = []byte{}
data []byte
exp int64 = 0
)
if err := row.Scan(&data, &exp); err != nil {
if err == sql.ErrNoRows {
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
return nil, err
@@ -163,7 +133,7 @@ func (s *Storage) Set(key string, val []byte, exp time.Duration) error {
if exp != 0 {
expSeconds = time.Now().Add(exp).Unix()
}
_, err := s.db.Exec(s.sqlInsert, key, val, expSeconds, val, expSeconds)
_, err := s.db.Exec(context.Background(), s.sqlInsert, key, val, expSeconds, val, expSeconds)
return err
}
@@ -173,24 +143,26 @@ func (s *Storage) Delete(key string) error {
if len(key) <= 0 {
return nil
}
_, err := s.db.Exec(s.sqlDelete, key)
_, err := s.db.Exec(context.Background(), s.sqlDelete, key)
return err
}
// Reset all entries, including unexpired
func (s *Storage) Reset() error {
_, err := s.db.Exec(s.sqlReset)
_, err := s.db.Exec(context.Background(), s.sqlReset)
return err
}
// Close the database
func (s *Storage) Close() error {
s.done <- struct{}{}
return s.db.Close()
s.db.Stat()
s.db.Close()
return nil
}
// Return database client
func (s *Storage) Conn() *sql.DB {
func (s *Storage) Conn() *pgxpool.Pool {
return s.db
}
@@ -210,13 +182,13 @@ func (s *Storage) gcTicker() {
// gc deletes all expired entries
func (s *Storage) gc(t time.Time) {
_, _ = s.db.Exec(s.sqlGC, t.Unix())
_, _ = s.db.Exec(context.Background(), s.sqlGC, t.Unix())
}
func (s *Storage) checkSchema(tableName string) {
var data []byte
row := s.db.QueryRow(fmt.Sprintf(checkSchemaQuery, tableName))
row := s.db.QueryRow(context.Background(), fmt.Sprintf(checkSchemaQuery, tableName))
if err := row.Scan(&data); err != nil {
panic(err)
}

View File

@@ -1,12 +1,13 @@
package postgres
import (
"database/sql"
"context"
"os"
"testing"
"time"
"github.com/gofiber/utils"
"github.com/jackc/pgx/v5"
)
var testStore = New(Config{
@@ -133,9 +134,9 @@ func Test_Postgres_GC(t *testing.T) {
utils.AssertEqual(t, nil, err)
testStore.gc(time.Now())
row := testStore.db.QueryRow(testStore.sqlSelect, "john")
row := testStore.db.QueryRow(context.Background(), testStore.sqlSelect, "john")
err = row.Scan(nil, nil)
utils.AssertEqual(t, sql.ErrNoRows, err)
utils.AssertEqual(t, pgx.ErrNoRows, err)
// This key should not expire
err = testStore.Set("john", testVal, 0)
@@ -166,18 +167,14 @@ func Test_SslRequiredMode(t *testing.T) {
}
}()
_ = New(Config{
Database: "fiber",
Username: "username",
Password: "password",
Reset: true,
SslMode: "require",
Reset: true,
})
}
func Test_Postgres_Close(t *testing.T) {
utils.AssertEqual(t, nil, testStore.Close())
}
func Test_Postgres_Conn(t *testing.T) {
utils.AssertEqual(t, true, testStore.Conn() != nil)
}
func Test_Postgres_Close(t *testing.T) {
utils.AssertEqual(t, nil, testStore.Close())
}