2025-03-15 15:03:34 +03:00
package storage
import (
"context"
"strconv"
"time"
"github.com/jackc/pgx/v5/pgxpool"
2025-03-16 17:54:51 +03:00
"github.com/rs/zerolog/log"
2025-03-15 15:03:34 +03:00
)
const setSQLQuery = "INSERT INTO entries (key, value, expiration) VALUES ($1, $2, $3) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, expiration = EXCLUDED.expiration"
const getSQLQuery = "SELECT id, value, expiration FROM entries WHERE key = $1"
const deleteSQLQuery = "DELETE FROM entries WHERE id = $1"
const updateSQLQuery = "UPDATE entries SET expiration = $1 WHERE id = $2"
type PostgresStorage struct {
pool * pgxpool . Pool
expiration int
}
var _ Storage = ( * PostgresStorage ) ( nil )
func NewPostgresStorage ( host string , port int , username string , passowrd string , database string , expiration int ) * PostgresStorage {
dsn := "postgres://" + username + ":" + passowrd + "@" + host + ":" + strconv . Itoa ( port ) + "/" + database
pool , err := pgxpool . New ( context . Background ( ) , dsn )
if err != nil {
2025-03-16 17:54:51 +03:00
log . Fatal ( ) . Err ( err ) . Msg ( "Failed to connect to PostgreSQL" )
2025-03-15 15:03:34 +03:00
}
// Check if connection is established
if err := pool . Ping ( context . Background ( ) ) ; err != nil {
2025-03-16 17:54:51 +03:00
log . Fatal ( ) . Err ( err ) . Msg ( "Failed to connect to PostgreSQL" )
2025-03-15 15:03:34 +03:00
}
// Create table if not exists
_ , err = pool . Exec ( context . Background ( ) , "CREATE TABLE IF NOT EXISTS entries (id SERIAL PRIMARY KEY, key VARCHAR(255) NOT NULL UNIQUE, value TEXT, expiration BIGINT)" )
if err != nil {
2025-03-16 17:54:51 +03:00
log . Fatal ( ) . Err ( err ) . Msg ( "Failed to create table" )
2025-03-15 15:03:34 +03:00
}
return & PostgresStorage { pool : pool , expiration : expiration }
}
func ( s * PostgresStorage ) Set ( key string , value string , skip_expiration bool ) error {
ctx := context . Background ( ) // TODO: Add timeout control
now := time . Now ( )
expiration := now . Add ( time . Duration ( s . expiration ) ) . Unix ( )
if skip_expiration {
expiration = 0
}
_ , err := s . pool . Exec ( ctx , setSQLQuery , key , value , expiration )
return err
}
func ( s * PostgresStorage ) Get ( key string , skip_expiration bool ) ( string , error ) {
ctx := context . Background ( ) // TODO: Add timeout control
var id int
var value string
var expiration int64
err := s . pool . QueryRow ( ctx , getSQLQuery , key ) . Scan ( & id , & value , & expiration )
if err != nil {
return "" , err
}
// Delete if expired
if expiration != 0 && time . Now ( ) . Unix ( ) > expiration {
_ , err = s . pool . Exec ( ctx , deleteSQLQuery , id )
if err != nil {
return "" , err
}
return "" , nil
}
// Update expiration
if ! skip_expiration {
_ , err = s . pool . Exec ( ctx , updateSQLQuery , time . Now ( ) . Add ( time . Duration ( s . expiration ) ) . Unix ( ) , id )
if err != nil {
return "" , err
}
}
return value , nil
}
2025-03-16 13:32:49 +03:00
func ( s * PostgresStorage ) Close ( ) error {
s . pool . Close ( )
return nil
}