proxy-pool/internal/adapters/postgresadmin/adapter.go

106 lines
2.4 KiB
Go

package postgresadmin
import (
"context"
"errors"
"fmt"
"reflect"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"proxy-pool/internal/domain/adminstate"
)
var _ adminstate.Store = (*Adapter)(nil)
type transactionBeginner interface {
BeginTx(context.Context, pgx.TxOptions) (pgx.Tx, error)
}
// Adapter keeps all PostgreSQL transaction and SQL details behind adminstate.Store.
type Adapter struct {
pool transactionBeginner
}
// New constructs the PostgreSQL management-state store.
func New(pool transactionBeginner) (adminstate.Store, error) {
if isNil(pool) {
return nil, adminstate.ErrInvalidCommand
}
return &Adapter{pool: pool}, nil
}
func isNil(value any) bool {
if value == nil {
return true
}
kind := reflect.ValueOf(value).Kind()
return (kind == reflect.Chan || kind == reflect.Func || kind == reflect.Interface ||
kind == reflect.Map || kind == reflect.Pointer || kind == reflect.Slice) &&
reflect.ValueOf(value).IsNil()
}
func contextError(ctx context.Context) error {
if ctx == nil {
return adminstate.ErrInvalidCommand
}
return ctx.Err()
}
func (adapter *Adapter) valid() bool {
return adapter != nil && !isNil(adapter.pool)
}
func (adapter *Adapter) begin(ctx context.Context, options pgx.TxOptions, operation string) (pgx.Tx, error) {
tx, err := adapter.pool.BeginTx(ctx, options)
if err != nil {
return nil, databaseError(ctx, operation, err)
}
if isNil(tx) {
return nil, unavailable(operation)
}
return tx, nil
}
func rollback(tx pgx.Tx) {
if !isNil(tx) {
_ = tx.Rollback(context.Background())
}
}
func commit(ctx context.Context, tx pgx.Tx, operation string) error {
if err := tx.Commit(ctx); err != nil {
return databaseError(ctx, operation, err)
}
return nil
}
func databaseError(ctx context.Context, operation string, err error) error {
if err == nil {
return nil
}
if ctx != nil && ctx.Err() != nil {
return ctx.Err()
}
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
return err
}
sentinel := adminstate.ErrUnavailable
var postgresError *pgconn.PgError
if errors.As(err, &postgresError) {
switch postgresError.Code {
case "23505":
sentinel = adminstate.ErrConflict
case "22001", "22003", "22P02":
sentinel = adminstate.ErrInvalidCommand
}
}
return fmt.Errorf("postgresadmin: %s: %w", operation, sentinel)
}
func unavailable(operation string) error {
return fmt.Errorf("postgresadmin: %s: %w", operation, adminstate.ErrUnavailable)
}