65 lines
1.7 KiB
Go
65 lines
1.7 KiB
Go
package mailstore
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgconn"
|
|
)
|
|
|
|
// pgUniqueViolationCode identifies the SQLSTATE returned by PostgreSQL when
|
|
// a UNIQUE constraint is violated by INSERT or UPDATE.
|
|
const pgUniqueViolationCode = "23505"
|
|
|
|
// isUniqueViolation reports whether err is a PostgreSQL unique-violation,
|
|
// regardless of constraint name.
|
|
func isUniqueViolation(err error) bool {
|
|
var pgErr *pgconn.PgError
|
|
if !errors.As(err, &pgErr) {
|
|
return false
|
|
}
|
|
return pgErr.Code == pgUniqueViolationCode
|
|
}
|
|
|
|
// nullableTime returns t.UTC() when non-nil, otherwise nil for NULL columns.
|
|
func nullableTime(t *time.Time) any {
|
|
if t == nil {
|
|
return nil
|
|
}
|
|
return t.UTC()
|
|
}
|
|
|
|
// isNoRows reports whether err is sql.ErrNoRows.
|
|
func isNoRows(err error) bool {
|
|
return errors.Is(err, sql.ErrNoRows)
|
|
}
|
|
|
|
// timeFromNullable copies an optional *time.Time read from Postgres into a
|
|
// new pointer normalised to UTC.
|
|
func timeFromNullable(value *time.Time) *time.Time {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
utc := value.UTC()
|
|
return &utc
|
|
}
|
|
|
|
// withTimeout derives a child context bounded by timeout and prefixes context
|
|
// errors with operation. Callers must always invoke the returned cancel.
|
|
func withTimeout(ctx context.Context, operation string, timeout time.Duration) (context.Context, context.CancelFunc, error) {
|
|
if ctx == nil {
|
|
return nil, nil, fmt.Errorf("%s: nil context", operation)
|
|
}
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, nil, fmt.Errorf("%s: %w", operation, err)
|
|
}
|
|
if timeout <= 0 {
|
|
return nil, nil, fmt.Errorf("%s: operation timeout must be positive", operation)
|
|
}
|
|
bounded, cancel := context.WithTimeout(ctx, timeout)
|
|
return bounded, cancel, nil
|
|
}
|