Files
Chrys 8b9ed92a11 fix(install): handle dirty one-click upgrades (#401)
* fix(install): handle dirty one-click upgrades

* fix(install): address dirty state review feedback
2026-04-26 17:20:18 +08:00

82 lines
1.8 KiB
Go

package db
import (
"errors"
"fmt"
"net"
"net/url"
"strconv"
"strings"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgconn"
"github.com/jackc/pgx/v5/pgtype"
"github.com/memohai/memoh/internal/config"
)
// DSN builds a PostgreSQL connection string from config.
func DSN(cfg config.PostgresConfig) string {
dsn := &url.URL{
Scheme: "postgres",
User: url.UserPassword(cfg.User, cfg.Password),
Host: net.JoinHostPort(cfg.Host, strconv.Itoa(cfg.Port)),
Path: cfg.Database,
}
query := dsn.Query()
query.Set("sslmode", cfg.SSLMode)
dsn.RawQuery = query.Encode()
return dsn.String()
}
// ParseUUID converts a string UUID to pgtype.UUID.
func ParseUUID(id string) (pgtype.UUID, error) {
parsed, err := uuid.Parse(strings.TrimSpace(id))
if err != nil {
return pgtype.UUID{}, fmt.Errorf("invalid UUID: %w", err)
}
var pgID pgtype.UUID
pgID.Valid = true
copy(pgID.Bytes[:], parsed[:])
return pgID, nil
}
// ParseUUIDOrEmpty converts a string UUID to pgtype.UUID, returning an invalid UUID if the string is empty or unparsable.
func ParseUUIDOrEmpty(id string) pgtype.UUID {
id = strings.TrimSpace(id)
if id == "" {
return pgtype.UUID{}
}
pgID, err := ParseUUID(id)
if err != nil {
return pgtype.UUID{}
}
return pgID
}
// TimeFromPg converts a pgtype.Timestamptz to time.Time.
func TimeFromPg(value pgtype.Timestamptz) time.Time {
if value.Valid {
return value.Time
}
return time.Time{}
}
// TextToString returns the string value of pgtype.Text, or "" when invalid.
func TextToString(value pgtype.Text) string {
if !value.Valid {
return ""
}
return value.String
}
// IsUniqueViolation reports whether err is a PostgreSQL unique constraint violation (SQLSTATE 23505).
func IsUniqueViolation(err error) bool {
var pgErr *pgconn.PgError
if !errors.As(err, &pgErr) {
return false
}
return pgErr.Code == "23505"
}