mirror of https://github.com/gabehf/Koito.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
120 lines
2.9 KiB
120 lines
2.9 KiB
// package psql implements the db.DB interface using psx and a sql generated repository
|
|
package psql
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"path/filepath"
|
|
"runtime"
|
|
"time"
|
|
|
|
"github.com/gabehf/koito/internal/cfg"
|
|
"github.com/gabehf/koito/internal/db"
|
|
"github.com/gabehf/koito/internal/repository"
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
_ "github.com/jackc/pgx/v5/stdlib"
|
|
"github.com/pressly/goose/v3"
|
|
)
|
|
|
|
const (
|
|
DefaultItemsPerPage = 20
|
|
)
|
|
|
|
type Psql struct {
|
|
q *repository.Queries
|
|
conn *pgxpool.Pool
|
|
}
|
|
|
|
func New() (*Psql, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
config, err := pgxpool.ParseConfig(cfg.DatabaseUrl())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse pgx config: %w", err)
|
|
}
|
|
|
|
config.ConnConfig.ConnectTimeout = 15 * time.Second
|
|
|
|
pool, err := pgxpool.NewWithConfig(ctx, config)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create pgx pool: %w", err)
|
|
}
|
|
|
|
if err := pool.Ping(ctx); err != nil {
|
|
pool.Close()
|
|
return nil, fmt.Errorf("database not reachable: %w", err)
|
|
}
|
|
|
|
sqlDB, err := sql.Open("pgx", cfg.DatabaseUrl())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open db for migrations: %w", err)
|
|
}
|
|
|
|
_, filename, _, ok := runtime.Caller(0)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unable to get caller info")
|
|
}
|
|
migrationsPath := filepath.Join(filepath.Dir(filename), "..", "..", "..", "db", "migrations")
|
|
|
|
if err := goose.Up(sqlDB, migrationsPath); err != nil {
|
|
return nil, fmt.Errorf("goose failed: %w", err)
|
|
}
|
|
_ = sqlDB.Close()
|
|
|
|
return &Psql{
|
|
q: repository.New(pool),
|
|
conn: pool,
|
|
}, nil
|
|
}
|
|
|
|
// Not part of the DB interface this package implements. Only used for testing.
|
|
func (d *Psql) Exec(ctx context.Context, query string, args ...any) error {
|
|
_, err := d.conn.Exec(ctx, query, args...)
|
|
return err
|
|
}
|
|
|
|
// Not part of the DB interface this package implements. Only used for testing.
|
|
func (d *Psql) RowExists(ctx context.Context, query string, args ...any) (bool, error) {
|
|
var exists bool
|
|
err := d.conn.QueryRow(ctx, query, args...).Scan(&exists)
|
|
return exists, err
|
|
}
|
|
|
|
func (p *Psql) Count(ctx context.Context, query string, args ...any) (count int, err error) {
|
|
err = p.conn.QueryRow(ctx, query, args...).Scan(&count)
|
|
return
|
|
}
|
|
|
|
// Exposes p.conn.QueryRow. Only used for testing. Not part of the DB interface this package implements.
|
|
func (p *Psql) QueryRow(ctx context.Context, query string, args ...any) pgx.Row {
|
|
return p.conn.QueryRow(ctx, query, args...)
|
|
}
|
|
|
|
func (d *Psql) Close(ctx context.Context) {
|
|
d.conn.Close()
|
|
}
|
|
|
|
func (d *Psql) Ping(ctx context.Context) error {
|
|
return d.conn.Ping(ctx)
|
|
}
|
|
|
|
func stepToInterval(p db.StepInterval) pgtype.Interval {
|
|
var interval pgtype.Interval
|
|
switch p {
|
|
case db.StepDay:
|
|
interval.Days = 1
|
|
case db.StepWeek:
|
|
interval.Days = 7
|
|
case db.StepMonth:
|
|
interval.Months = 1
|
|
case db.StepYear:
|
|
interval.Months = 12
|
|
}
|
|
interval.Valid = true
|
|
return interval
|
|
}
|