Compare commits

...

1 Commits

Author SHA1 Message Date
48f922ef6c feat: wire TUI settings to backend + pgzip consistency
All checks were successful
CI/CD / Test (push) Successful in 3m3s
CI/CD / Lint (push) Successful in 1m10s
CI/CD / Integration Tests (push) Successful in 50s
CI/CD / Native Engine Tests (push) Successful in 52s
CI/CD / Build Binary (push) Successful in 44s
CI/CD / Test Release Build (push) Successful in 1m22s
CI/CD / Release Binaries (push) Successful in 10m5s
- Add native engine support for restore (cmd/native_restore.go)
- Integrate native engine restore into cmd/restore.go with fallback
- Fix CPUWorkloadType to auto-detect CPU if CPUInfo is nil
- Replace standard gzip with pgzip in native_backup.go
- All compression now uses parallel pgzip consistently

Bump version to 5.4.2
2026-02-02 12:11:24 +01:00
5 changed files with 123 additions and 4 deletions

View File

@ -1,7 +1,6 @@
package cmd
import (
"compress/gzip"
"context"
"fmt"
"io"
@ -12,6 +11,8 @@ import (
"dbbackup/internal/database"
"dbbackup/internal/engine/native"
"dbbackup/internal/notify"
"github.com/klauspost/pgzip"
)
// runNativeBackup executes backup using native Go engines
@ -58,10 +59,13 @@ func runNativeBackup(ctx context.Context, db database.Database, databaseName, ba
}
defer file.Close()
// Wrap with compression if enabled
// Wrap with compression if enabled (use pgzip for parallel compression)
var writer io.Writer = file
if cfg.CompressionLevel > 0 {
gzWriter := gzip.NewWriter(file)
gzWriter, err := pgzip.NewWriterLevel(file, cfg.CompressionLevel)
if err != nil {
return fmt.Errorf("failed to create gzip writer: %w", err)
}
defer gzWriter.Close()
writer = gzWriter
}

93
cmd/native_restore.go Normal file
View File

@ -0,0 +1,93 @@
package cmd
import (
"context"
"fmt"
"io"
"os"
"time"
"dbbackup/internal/database"
"dbbackup/internal/engine/native"
"dbbackup/internal/notify"
"github.com/klauspost/pgzip"
)
// runNativeRestore executes restore using native Go engines
func runNativeRestore(ctx context.Context, db database.Database, archivePath, targetDB string, cleanFirst, createIfMissing bool, startTime time.Time, user string) error {
// Initialize native engine manager
engineManager := native.NewEngineManager(cfg, log)
if err := engineManager.InitializeEngines(ctx); err != nil {
return fmt.Errorf("failed to initialize native engines: %w", err)
}
defer engineManager.Close()
// Check if native engine is available for this database type
dbType := detectDatabaseTypeFromConfig()
if !engineManager.IsNativeEngineAvailable(dbType) {
return fmt.Errorf("native restore engine not available for database type: %s", dbType)
}
// Open archive file
file, err := os.Open(archivePath)
if err != nil {
return fmt.Errorf("failed to open archive: %w", err)
}
defer file.Close()
// Detect if file is gzip compressed
var reader io.Reader = file
if isGzipFile(archivePath) {
gzReader, err := pgzip.NewReader(file)
if err != nil {
return fmt.Errorf("failed to create gzip reader: %w", err)
}
defer gzReader.Close()
reader = gzReader
}
log.Info("Starting native restore",
"archive", archivePath,
"database", targetDB,
"engine", dbType,
"clean_first", cleanFirst,
"create_if_missing", createIfMissing)
// Perform restore using native engine
if err := engineManager.RestoreWithNativeEngine(ctx, reader, targetDB); err != nil {
auditLogger.LogRestoreFailed(user, targetDB, err)
if notifyManager != nil {
notifyManager.Notify(notify.NewEvent(notify.EventRestoreFailed, notify.SeverityError, "Native restore failed").
WithDatabase(targetDB).
WithError(err))
}
return fmt.Errorf("native restore failed: %w", err)
}
restoreDuration := time.Since(startTime)
log.Info("Native restore completed successfully",
"database", targetDB,
"duration", restoreDuration,
"engine", dbType)
// Audit log: restore completed
auditLogger.LogRestoreComplete(user, targetDB, restoreDuration)
// Notify: restore completed
if notifyManager != nil {
notifyManager.Notify(notify.NewEvent(notify.EventRestoreCompleted, notify.SeverityInfo, "Native restore completed").
WithDatabase(targetDB).
WithDuration(restoreDuration).
WithDetail("engine", dbType))
}
return nil
}
// isGzipFile checks if file has gzip extension
func isGzipFile(path string) bool {
return len(path) > 3 && path[len(path)-3:] == ".gz"
}

View File

@ -720,6 +720,23 @@ func runRestoreSingle(cmd *cobra.Command, args []string) error {
WithDetail("archive", filepath.Base(archivePath)))
}
// Check if native engine should be used for restore
if cfg.UseNativeEngine {
log.Info("Using native engine for restore", "database", targetDB)
err = runNativeRestore(ctx, db, archivePath, targetDB, restoreClean, restoreCreate, startTime, user)
if err != nil && cfg.FallbackToTools {
log.Warn("Native engine restore failed, falling back to external tools", "error", err)
// Continue with tool-based restore below
} else {
// Native engine succeeded or no fallback configured
if err == nil {
log.Info("[OK] Restore completed successfully (native engine)", "database", targetDB)
}
return err
}
}
if err := engine.RestoreSingle(ctx, archivePath, targetDB, restoreClean, restoreCreate); err != nil {
auditLogger.LogRestoreFailed(user, targetDB, err)
// Notify: restore failed

View File

@ -94,6 +94,11 @@ func NewSettingsModel(cfg *config.Config, log logger.Logger, parent tea.Model) S
c.CPUWorkloadType = workloads[nextIdx]
// Recalculate Jobs and DumpJobs based on workload type
// If CPUInfo is nil, try to detect it first
if c.CPUInfo == nil && c.AutoDetectCores {
_ = c.OptimizeForCPU() // This will detect CPU and set CPUInfo
}
if c.CPUInfo != nil && c.AutoDetectCores {
switch c.CPUWorkloadType {
case "cpu-intensive":

View File

@ -16,7 +16,7 @@ import (
// Build information (set by ldflags)
var (
version = "5.4.1"
version = "5.4.2"
buildTime = "unknown"
gitCommit = "unknown"
)