restore: add critical PostgreSQL restore flags per official documentation

Based on PostgreSQL documentation research (postgresql.org/docs/current/app-pgrestore.html):

CRITICAL FIXES:
- Add --exit-on-error: pg_restore continues on errors by default, masking failures
- Add --no-data-for-failed-tables: prevents duplicate data in existing tables
- Use template0 for CREATE DATABASE: avoids duplicate definition errors from template1 additions
- Fix --jobs incompatibility: cannot use with --single-transaction per docs

WHY THIS MATTERS:
- Without --exit-on-error, pg_restore returns success even with failures
- Without --no-data-for-failed-tables, restore fails on existing objects
- template1 may have local additions causing 'duplicate definition' errors
- --jobs with --single-transaction causes pg_restore to fail

This should resolve the 'exit status 1' cluster restore failures.
This commit is contained in:
2025-11-13 12:54:44 +00:00
parent b9b44dd989
commit 58d11bc4b3
2 changed files with 14 additions and 5 deletions

View File

@@ -349,8 +349,8 @@ func (p *PostgreSQL) BuildRestoreCommand(database, inputFile string, options Res
} }
cmd = append(cmd, "-U", p.cfg.User) cmd = append(cmd, "-U", p.cfg.User)
// Parallel jobs // Parallel jobs (incompatible with --single-transaction per PostgreSQL docs)
if options.Parallel > 1 { if options.Parallel > 1 && !options.SingleTransaction {
cmd = append(cmd, "--jobs="+strconv.Itoa(options.Parallel)) cmd = append(cmd, "--jobs="+strconv.Itoa(options.Parallel))
} }
@@ -371,6 +371,13 @@ func (p *PostgreSQL) BuildRestoreCommand(database, inputFile string, options Res
cmd = append(cmd, "--single-transaction") cmd = append(cmd, "--single-transaction")
} }
// CRITICAL: Exit on first error (by default pg_restore continues on errors)
// This ensures we catch failures immediately instead of at the end
cmd = append(cmd, "--exit-on-error")
// Skip data restore if table creation fails (prevents duplicate data errors)
cmd = append(cmd, "--no-data-for-failed-tables")
// Add verbose flag for better error reporting // Add verbose flag for better error reporting
cmd = append(cmd, "--verbose") cmd = append(cmd, "--verbose")

View File

@@ -865,13 +865,15 @@ func (e *Engine) ensureDatabaseExists(ctx context.Context, dbName string) error
} }
// Database doesn't exist, create it // Database doesn't exist, create it
e.log.Info("Creating database", "name", dbName) // IMPORTANT: Use template0 to avoid duplicate definition errors from local additions to template1
// See PostgreSQL docs: https://www.postgresql.org/docs/current/app-pgrestore.html#APP-PGRESTORE-NOTES
e.log.Info("Creating database from template0", "name", dbName)
createArgs := []string{ createArgs := []string{
"-p", fmt.Sprintf("%d", e.cfg.Port), "-p", fmt.Sprintf("%d", e.cfg.Port),
"-U", e.cfg.User, "-U", e.cfg.User,
"-d", "postgres", "-d", "postgres",
"-c", fmt.Sprintf("CREATE DATABASE \"%s\"", dbName), "-c", fmt.Sprintf("CREATE DATABASE \"%s\" WITH TEMPLATE template0", dbName),
} }
// Only add -h flag if host is not localhost (to use Unix socket for peer auth) // Only add -h flag if host is not localhost (to use Unix socket for peer auth)
@@ -891,7 +893,7 @@ func (e *Engine) ensureDatabaseExists(ctx context.Context, dbName string) error
return fmt.Errorf("failed to create database '%s': %w (output: %s)", dbName, err, strings.TrimSpace(string(output))) return fmt.Errorf("failed to create database '%s': %w (output: %s)", dbName, err, strings.TrimSpace(string(output)))
} }
e.log.Info("Successfully created database", "name", dbName) e.log.Info("Successfully created database from template0", "name", dbName)
return nil return nil
} }