Skip to content

Commit 77d6956

Browse files
committed
feat: include/restore statistics in snapshots (PG18+)
1 parent 412a791 commit 77d6956

3 files changed

Lines changed: 49 additions & 17 deletions

File tree

cmd/snapshot.go

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -389,19 +389,25 @@ func runSnapshotRestore() error {
389389
return fmt.Errorf("database connection failed: %w", err)
390390
}
391391

392+
// Connect to check database state and version
393+
db, err := regresql.OpenDB(cfg.PgUri)
394+
if err != nil {
395+
return fmt.Errorf("failed to connect: %w", err)
396+
}
397+
defer db.Close()
398+
392399
// Check if database has existing tables and warn if --clean not specified
393400
if !snapshotClean {
394-
db, err := regresql.OpenDB(cfg.PgUri)
395-
if err == nil {
396-
defer db.Close()
397-
var tableCount int
398-
db.QueryRow("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE'").Scan(&tableCount)
399-
if tableCount > 0 {
400-
return fmt.Errorf("database has %d existing table(s). Use --clean to drop them before restore, or manually clear the database", tableCount)
401-
}
401+
var tableCount int
402+
db.QueryRow("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE'").Scan(&tableCount)
403+
if tableCount > 0 {
404+
return fmt.Errorf("database has %d existing table(s). Use --clean to drop them before restore, or manually clear the database", tableCount)
402405
}
403406
}
404407

408+
// Detect PostgreSQL version for statistics support
409+
serverCtx, _ := regresql.CaptureServerContext(db)
410+
405411
inputPath := snapshotInput
406412
if inputPath == "" {
407413
inputPath = regresql.GetSnapshotPath(cfg.Snapshot, snapshotCwd)
@@ -422,10 +428,12 @@ func runSnapshotRestore() error {
422428
return err
423429
}
424430

431+
withStats := serverCtx != nil && serverCtx.MajorVersion() >= 18
425432
opts := regresql.RestoreOptions{
426-
InputPath: inputPath,
427-
Format: format,
428-
Clean: snapshotClean,
433+
InputPath: inputPath,
434+
Format: format,
435+
Clean: snapshotClean,
436+
WithStatistics: withStats,
429437
}
430438

431439
fmt.Printf("Restoring database snapshot...\n")
@@ -434,6 +442,9 @@ func runSnapshotRestore() error {
434442
if snapshotClean {
435443
fmt.Printf(" Mode: clean (drop existing objects)\n")
436444
}
445+
if withStats {
446+
fmt.Printf(" Stats: restoring optimizer statistics (PG18+)\n")
447+
}
437448
fmt.Println()
438449

439450
if err := regresql.RestoreSnapshot(cfg.PgUri, opts); err != nil {

regresql/snapshot.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,11 @@ type (
7575
SnapshotFormat string
7676

7777
SnapshotOptions struct {
78-
OutputPath string
79-
Format SnapshotFormat
80-
SchemaOnly bool
81-
Section string
78+
OutputPath string
79+
Format SnapshotFormat
80+
SchemaOnly bool
81+
Section string
82+
WithStatistics bool // PostgreSQL 18+: include optimizer statistics
8283
}
8384

8485
SectionsOptions struct {
@@ -102,6 +103,7 @@ type (
102103
Format SnapshotFormat
103104
Clean bool // drop existing objects before restore
104105
TargetDatabase string // override database name from connection string
106+
WithStatistics bool // PostgreSQL 18+: restore optimizer statistics
105107
}
106108
)
107109

@@ -319,6 +321,11 @@ func buildPgDumpArgs(pguri string, opts SnapshotOptions) []string {
319321
args = append(args, "--section", opts.Section)
320322
}
321323

324+
// PostgreSQL 18+: include optimizer statistics in dump (requires pg_dump 18+)
325+
if opts.WithStatistics && parseToolMajorVersion("pg_dump") >= 18 {
326+
args = append(args, "--statistics")
327+
}
328+
322329
return args
323330
}
324331

@@ -588,6 +595,11 @@ func restoreWithPgRestore(pguri string, opts RestoreOptions, format SnapshotForm
588595
args = append(args, "--clean", "--if-exists")
589596
}
590597

598+
// PostgreSQL 18+: restore optimizer statistics (requires pg_restore 18+)
599+
if opts.WithStatistics && parseToolMajorVersion("pg_restore") >= 18 {
600+
args = append(args, "--statistics")
601+
}
602+
591603
switch format {
592604
case FormatCustom:
593605
args = append(args, "--format=custom")

regresql/snapshot_build.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,13 +147,22 @@ func BuildSnapshot(basePgUri string, root string, opts SnapshotBuildOptions) (*s
147147
return nil, fmt.Errorf("failed to capture server context: %w", err)
148148
}
149149

150+
// Run ANALYZE to ensure statistics are up to date before pg_dump
151+
if opts.Verbose {
152+
fmt.Printf("Running ANALYZE...\n")
153+
}
154+
if _, err := db.Exec("ANALYZE"); err != nil {
155+
return nil, fmt.Errorf("failed to analyze database: %w", err)
156+
}
157+
150158
if opts.Verbose {
151159
fmt.Printf("Capturing snapshot with pg_dump...\n")
152160
}
153161

154162
info, err := CaptureSnapshot(tempDB.PgUri, SnapshotOptions{
155-
OutputPath: opts.OutputPath,
156-
Format: opts.Format,
163+
OutputPath: opts.OutputPath,
164+
Format: opts.Format,
165+
WithStatistics: serverCtx.MajorVersion() >= 18,
157166
})
158167
if err != nil {
159168
return nil, fmt.Errorf("failed to capture snapshot: %w", err)

0 commit comments

Comments
 (0)