Files
goclaw/internal/backup/db_dump.go
T
viettranx 3b4004946e chore: apply go fix syntax upgrades (min/max, switch patterns)
Apply Go 1.21+ min/max builtins and improved switch pattern matching
across test files and utilities. No functional changes.
2026-04-15 11:24:57 +07:00

105 lines
2.3 KiB
Go

//go:build !sqliteonly
package backup
import (
"bytes"
"context"
"fmt"
"io"
"os"
"os/exec"
"strconv"
"strings"
)
// DumpDatabase runs pg_dump and streams plain-SQL output to w.
// Uses a temporary .pgpass file (0600) to pass the password securely.
// The child process receives only PGPASSFILE, PATH, HOME, LC_ALL=C.
func DumpDatabase(ctx context.Context, dsn string, w io.Writer) error {
creds, err := ParseDSN(dsn)
if err != nil {
return fmt.Errorf("parse DSN: %w", err)
}
pgDump, err := exec.LookPath("pg_dump")
if err != nil {
return fmt.Errorf("pg_dump not found on PATH: %w", err)
}
tempDir, pgpassPath, err := WritePgpass(creds)
if err != nil {
return err
}
defer os.RemoveAll(tempDir)
args := []string{
"--host", creds.Host,
"--port", creds.Port,
"--username", creds.User,
"--dbname", creds.DBName,
"--format=plain",
"--clean",
"--if-exists",
"--no-owner",
"--no-privileges",
}
cmd := exec.CommandContext(ctx, pgDump, args...)
cmd.Env = CleanEnv(pgpassPath)
cmd.Stdout = w
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
errMsg := strings.TrimSpace(stderr.String())
if errMsg == "" {
errMsg = err.Error()
}
return fmt.Errorf("pg_dump failed: %s", errMsg)
}
return nil
}
// PgDumpVersion returns the version string from pg_dump --version.
func PgDumpVersion(ctx context.Context) (string, error) {
pgDump, err := exec.LookPath("pg_dump")
if err != nil {
return "", fmt.Errorf("pg_dump not found: %w", err)
}
out, err := exec.CommandContext(ctx, pgDump, "--version").Output()
if err != nil {
return "", fmt.Errorf("pg_dump --version: %w", err)
}
return strings.TrimSpace(string(out)), nil
}
// ParsePgDumpMajor extracts the PostgreSQL major version number from a
// pg_dump --version string. Returns 0 if parsing fails.
// Example inputs:
//
// "pg_dump (PostgreSQL) 17.9 (Debian 17.9-1.pgdg12+1)" -> 17
// "pg_dump (PostgreSQL) 18.3" -> 18
func ParsePgDumpMajor(version string) int {
const marker = "(PostgreSQL) "
_, after, ok := strings.Cut(version, marker)
if !ok {
return 0
}
rest := after
end := 0
for end < len(rest) && rest[end] >= '0' && rest[end] <= '9' {
end++
}
if end == 0 {
return 0
}
major, err := strconv.Atoi(rest[:end])
if err != nil {
return 0
}
return major
}