Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added an ability to change default table/column names for postgres store #190

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions postgresstore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@ func getHandler(w http.ResponseWriter, r *http.Request) {
}
```

## Custom table and column names

In case you need custom names for the table or columns, you can use functional options:

```go
postgresstore.New(db,
postgresstore.WithSessionTableName("my_session_table"),
postgresstore.WithDataColumnName("my_data_column"),
postgresstore.WithTokenColumnName("my_token_column"),
postgresstore.WithExpiryColumnName("my_expiry_date_column"),
)
```

## Expired Session Cleanup

This package provides a background 'cleanup' goroutine to delete expired session data. This stops the database table from holding on to invalid sessions indefinitely and growing unnecessarily large. By default the cleanup runs every 5 minutes. You can change this by using the `NewWithCleanupInterval()` function to initialize your session store. For example:
Expand Down
45 changes: 45 additions & 0 deletions postgresstore/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package postgresstore

import (
"time"
)

type storeOptions struct {
sessionTableName string
dataColumnName string
tokenColumnName string
expiryColumnName string
cleanupInterval time.Duration
}

type StoreOption func(*storeOptions)

func WithSessionTableName(tableName string) StoreOption {
return func(options *storeOptions) {
options.sessionTableName = tableName
}
}

func WithDataColumnName(columnName string) StoreOption {
return func(options *storeOptions) {
options.dataColumnName = columnName
}
}

func WithTokenColumnName(columnName string) StoreOption {
return func(options *storeOptions) {
options.tokenColumnName = columnName
}
}

func WithExpiryColumnName(columnName string) StoreOption {
return func(options *storeOptions) {
options.expiryColumnName = columnName
}
}

func WithCleanupInterval(interval time.Duration) StoreOption {
return func(options *storeOptions) {
options.cleanupInterval = interval
}
}
62 changes: 50 additions & 12 deletions postgresstore/postgresstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package postgresstore

import (
"database/sql"
"fmt"
"log"
"time"
)
Expand All @@ -10,31 +11,54 @@ import (
type PostgresStore struct {
db *sql.DB
stopCleanup chan bool
opts *storeOptions
}

var defaultOptions = storeOptions{
sessionTableName: "sessions",
tokenColumnName: "token",
dataColumnName: "data",
expiryColumnName: "expiry",
cleanupInterval: 5 * time.Minute,
}

// New returns a new PostgresStore instance, with a background cleanup goroutine
// that runs every 5 minutes to remove expired session data.
func New(db *sql.DB) *PostgresStore {
return NewWithCleanupInterval(db, 5*time.Minute)
func New(db *sql.DB, options ...StoreOption) *PostgresStore {
storeOpts := defaultOptions

for _, opt := range options {
opt(&storeOpts)
}

p := &PostgresStore{
db: db,
opts: &storeOpts,
}

if p.opts.cleanupInterval > 0 {
go p.startCleanup(p.opts.cleanupInterval)
}

return p
}

// NewWithCleanupInterval returns a new PostgresStore instance. The cleanupInterval
// parameter controls how frequently expired session data is removed by the
// background cleanup goroutine. Setting it to 0 prevents the cleanup goroutine
// from running (i.e. expired sessions will not be removed).
func NewWithCleanupInterval(db *sql.DB, cleanupInterval time.Duration) *PostgresStore {
p := &PostgresStore{db: db}
if cleanupInterval > 0 {
go p.startCleanup(cleanupInterval)
}
return p
return New(db, WithCleanupInterval(cleanupInterval))
}

// Find returns the data for a given session token from the PostgresStore instance.
// If the session token is not found or is expired, the returned exists flag will
// be set to false.
func (p *PostgresStore) Find(token string) (b []byte, exists bool, err error) {
row := p.db.QueryRow("SELECT data FROM sessions WHERE token = $1 AND current_timestamp < expiry", token)
row := p.db.QueryRow(fmt.Sprintf(
"SELECT %s FROM %s WHERE %s = $1 AND current_timestamp < %s",
p.opts.dataColumnName, p.opts.sessionTableName, p.opts.tokenColumnName, p.opts.expiryColumnName,
), token)
err = row.Scan(&b)
if err == sql.ErrNoRows {
return nil, false, nil
Expand All @@ -48,7 +72,12 @@ func (p *PostgresStore) Find(token string) (b []byte, exists bool, err error) {
// given expiry time. If the session token already exists, then the data and expiry
// time are updated.
func (p *PostgresStore) Commit(token string, b []byte, expiry time.Time) error {
_, err := p.db.Exec("INSERT INTO sessions (token, data, expiry) VALUES ($1, $2, $3) ON CONFLICT (token) DO UPDATE SET data = EXCLUDED.data, expiry = EXCLUDED.expiry", token, b, expiry)
_, err := p.db.Exec(fmt.Sprintf(
"INSERT INTO %s (%s, %s, %s) VALUES ($1, $2, $3) ON CONFLICT (%s) DO UPDATE SET %s = EXCLUDED.%s, %s = EXCLUDED.%s",
p.opts.sessionTableName, p.opts.tokenColumnName, p.opts.dataColumnName, p.opts.expiryColumnName,
p.opts.tokenColumnName, p.opts.dataColumnName, p.opts.dataColumnName, p.opts.expiryColumnName,
p.opts.expiryColumnName,
), token, b, expiry)
if err != nil {
return err
}
Expand All @@ -58,14 +87,20 @@ func (p *PostgresStore) Commit(token string, b []byte, expiry time.Time) error {
// Delete removes a session token and corresponding data from the PostgresStore
// instance.
func (p *PostgresStore) Delete(token string) error {
_, err := p.db.Exec("DELETE FROM sessions WHERE token = $1", token)
_, err := p.db.Exec(fmt.Sprintf(
"DELETE FROM %s WHERE %s = $1",
p.opts.sessionTableName, p.opts.tokenColumnName,
), token)
return err
}

// All returns a map containing the token and data for all active (i.e.
// not expired) sessions in the PostgresStore instance.
func (p *PostgresStore) All() (map[string][]byte, error) {
rows, err := p.db.Query("SELECT token, data FROM sessions WHERE current_timestamp < expiry")
rows, err := p.db.Query(fmt.Sprintf(
"SELECT %s, %s FROM %s WHERE current_timestamp < %s",
p.opts.tokenColumnName, p.opts.dataColumnName, p.opts.sessionTableName, p.opts.expiryColumnName,
))
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -129,6 +164,9 @@ func (p *PostgresStore) StopCleanup() {
}

func (p *PostgresStore) deleteExpired() error {
_, err := p.db.Exec("DELETE FROM sessions WHERE expiry < current_timestamp")
_, err := p.db.Exec(fmt.Sprintf(
"DELETE FROM %s WHERE %s < current_timestamp",
p.opts.sessionTableName, p.opts.expiryColumnName,
))
return err
}