Skip to content

Commit 97b8265

Browse files
committed
feat: capture PostgreSQL server setting & basic settings affecting planner costs
I.e. buffers don't lie, and costs is considered stable (well below the treshold of 10% used at the moment), if we can trust the setting is same.
1 parent 4720759 commit 97b8265

5 files changed

Lines changed: 322 additions & 2 deletions

File tree

cmd/snapshot.go

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ var (
2424
snapshotBuildSchema string
2525
snapshotBuildMigrations string
2626
snapshotBuildVerbose bool
27+
snapshotInfoCompare bool
2728

2829
snapshotCmd = &cobra.Command{
2930
Use: "snapshot",
@@ -109,10 +110,13 @@ Examples:
109110
Short: "Display snapshot metadata",
110111
Long: `Display metadata about the current snapshot.
111112
112-
Shows the snapshot path, hash, size, creation time, and fixtures used.
113+
Shows the snapshot path, hash, size, creation time, server version, planner settings, and fixtures used.
114+
115+
Use --compare to compare stored settings with current database settings.
113116
114117
Examples:
115-
regresql snapshot info`,
118+
regresql snapshot info
119+
regresql snapshot info --compare`,
116120
Run: func(cmd *cobra.Command, args []string) {
117121
if err := checkDirectory(snapshotCwd); err != nil {
118122
fmt.Print(err.Error())
@@ -152,6 +156,8 @@ func init() {
152156
snapshotBuildCmd.Flags().StringVar(&snapshotBuildMigrations, "migrations", "", "Directory of SQL migrations to apply")
153157
snapshotBuildCmd.Flags().StringSliceVar(&snapshotBuildFixtures, "fixtures", nil, "Fixture names to apply")
154158
snapshotBuildCmd.Flags().BoolVarP(&snapshotBuildVerbose, "verbose", "v", false, "Print detailed progress")
159+
160+
snapshotInfoCmd.Flags().BoolVar(&snapshotInfoCompare, "compare", false, "Compare stored settings with current database")
155161
}
156162

157163
func validateSnapshotPrereqs(pguri string) error {
@@ -497,6 +503,9 @@ func runSnapshotBuild() error {
497503
if len(result.FixturesUsed) > 0 {
498504
fmt.Printf(" Fixtures: %d applied\n", len(result.FixturesUsed))
499505
}
506+
if result.Info.Server != nil {
507+
fmt.Printf(" Server: PostgreSQL %d\n", result.Info.Server.MajorVersion())
508+
}
500509

501510
return nil
502511
}
@@ -556,5 +565,72 @@ func runSnapshotInfo() error {
556565
}
557566
}
558567

568+
if info.Server != nil {
569+
fmt.Println()
570+
fmt.Printf("Server: PostgreSQL %s\n", info.Server.Version)
571+
if len(info.Server.PlannerSettings) > 0 {
572+
fmt.Println()
573+
fmt.Println("Planner Settings:")
574+
for _, name := range regresql.PlannerSettings {
575+
if val, ok := info.Server.PlannerSettings[name]; ok {
576+
fmt.Printf(" %s: %s\n", name, val)
577+
}
578+
}
579+
}
580+
}
581+
582+
if snapshotInfoCompare {
583+
if err := runSnapshotInfoCompare(info); err != nil {
584+
return err
585+
}
586+
}
587+
588+
return nil
589+
}
590+
591+
func runSnapshotInfoCompare(info *regresql.SnapshotInfo) error {
592+
if info.Server == nil {
593+
fmt.Println()
594+
fmt.Println("No server context stored - nothing to compare")
595+
return nil
596+
}
597+
598+
cfg, err := regresql.ReadConfig(snapshotCwd)
599+
if err != nil {
600+
return fmt.Errorf("failed to read config: %w", err)
601+
}
602+
603+
db, err := regresql.OpenDB(cfg.PgUri)
604+
if err != nil {
605+
return fmt.Errorf("failed to connect: %w", err)
606+
}
607+
defer db.Close()
608+
609+
validation, err := regresql.ValidateServerContext(db, info.Server)
610+
if err != nil {
611+
return fmt.Errorf("failed to compare: %w", err)
612+
}
613+
614+
fmt.Println()
615+
fmt.Println("Comparison with current database:")
616+
if !validation.HasDifferences() {
617+
fmt.Println(" [ok] All settings match")
618+
return nil
619+
}
620+
621+
if validation.VersionDiff != nil {
622+
marker := "[!]"
623+
if validation.MajorMismatch {
624+
marker = "[X]"
625+
}
626+
fmt.Printf(" %s version: %s -> %s\n", marker, info.Server.Version, validation.VersionDiff.Actual)
627+
} else {
628+
fmt.Printf(" [ok] version: %s\n", info.Server.Version)
629+
}
630+
631+
for _, d := range validation.SettingsDiffs {
632+
fmt.Printf(" [!] %s: %s -> %s\n", d.Name, d.Expected, d.Actual)
633+
}
634+
559635
return nil
560636
}

regresql/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type (
3636
Fixtures []string `yaml:"fixtures,omitempty"` // SQL/YAML fixture files for snapshot build
3737
AutoRestore *bool `yaml:"auto_restore,omitempty"` // restore snapshot before test (default: true if path is set)
3838
RestoreDatabase string `yaml:"restore_database,omitempty"` // override target database for restore
39+
ValidateSettings string `yaml:"validate_settings,omitempty"` // server settings validation: warn (default), strict, ignore
3940
}
4041
)
4142

regresql/regresql.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,35 @@ func Update(root string, runFilter string, commit, noRestore, forceRestore bool)
110110

111111
autoRestore(config, root, noRestore, forceRestore)
112112

113+
// Validate schema hasn't changed since last snapshot build
114+
if err := ValidateSchemaHash(root); err != nil {
115+
fmt.Printf("Error: %s\n", err)
116+
os.Exit(1)
117+
}
118+
119+
// Validate migrations haven't changed since last snapshot build
120+
if err := ValidateMigrationsHash(root); err != nil {
121+
fmt.Printf("Error: %s\n", err)
122+
os.Exit(1)
123+
}
124+
125+
// Validate migration command hasn't changed since last snapshot build
126+
if err := ValidateMigrationCommandHash(root); err != nil {
127+
fmt.Printf("Error: %s\n", err)
128+
os.Exit(1)
129+
}
130+
113131
if err := TestConnectionString(config.PgUri); err != nil {
114132
fmt.Print(err.Error())
115133
os.Exit(2)
116134
}
117135

136+
// Validate server settings match snapshot (warn, strict, or ignore)
137+
if err := validateServerSettings(config, root); err != nil {
138+
fmt.Printf("Error: %s\n", err)
139+
os.Exit(1)
140+
}
141+
118142
if err := suite.createExpectedResults(config.PgUri, commit); err != nil {
119143
fmt.Print(err.Error())
120144
os.Exit(12)
@@ -189,6 +213,47 @@ func autoRestore(cfg config, root string, noRestore, forceRestore bool) {
189213
fmt.Printf("Restored in %.1fs\n\n", duration.Seconds())
190214
}
191215

216+
func validateServerSettings(cfg config, root string) error {
217+
mode := GetValidateSettings(cfg.Snapshot)
218+
if mode == ValidateSettingsIgnore {
219+
return nil
220+
}
221+
222+
snapshotsDir := GetSnapshotsDir(root)
223+
metadata, err := ReadSnapshotMetadata(snapshotsDir)
224+
if err != nil {
225+
return nil // no metadata - nothing to validate
226+
}
227+
228+
if metadata.Current == nil || metadata.Current.Server == nil {
229+
return nil // no server context stored
230+
}
231+
232+
db, err := OpenDB(cfg.PgUri)
233+
if err != nil {
234+
return fmt.Errorf("failed to connect for server validation: %w", err)
235+
}
236+
defer db.Close()
237+
238+
validation, err := ValidateServerContext(db, metadata.Current.Server)
239+
if err != nil {
240+
return fmt.Errorf("failed to validate server context: %w", err)
241+
}
242+
243+
if !validation.HasDifferences() {
244+
return nil
245+
}
246+
247+
warning := validation.FormatWarning()
248+
249+
if mode == ValidateSettingsStrict {
250+
return fmt.Errorf("%s\n\nUse validate_settings: warn to continue with warnings", warning)
251+
}
252+
253+
fmt.Printf("%s\n\n", warning)
254+
return nil
255+
}
256+
192257
// Test runs regression tests for all queries.
193258
// Each query runs in its own transaction that rolls back (unless commit is true).
194259
func Test(root, runFilter, formatName, outputPath string, commit, noRestore, forceRestore bool) {
@@ -234,6 +299,12 @@ func Test(root, runFilter, formatName, outputPath string, commit, noRestore, for
234299
os.Exit(2)
235300
}
236301

302+
// Validate server settings match snapshot (warn, strict, or ignore)
303+
if err := validateServerSettings(config, root); err != nil {
304+
fmt.Printf("Error: %s\n", err)
305+
os.Exit(1)
306+
}
307+
237308
if formatName == "" {
238309
formatName = "console"
239310
}

0 commit comments

Comments
 (0)