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 } } return fmt.Errorf("postgresadmin: %s: %w", operation, sentinel) } func unavailable(operation string) error { return fmt.Errorf("postgresadmin: %s: %w", operation, adminstate.ErrUnavailable) }