diff --git a/internal/query/wal.go b/internal/query/wal.go index 50ce94f..e1862d6 100644 --- a/internal/query/wal.go +++ b/internal/query/wal.go @@ -21,10 +21,12 @@ const ( "FROM pg_stat_wal" ) -// SelectStatWALQuery returns the proper query and column count for pg_stat_wal based on PG version. -func SelectStatWALQuery(version int) (string, int) { +// SelectStatWALQuery returns the proper query, column count and diff interval for pg_stat_wal based on PG version. +func SelectStatWALQuery(version int) (string, int, [2]int) { if version >= 180000 { - return PgStatWALPG18, 7 + // PG 18 removed wal_write/wal_sync columns; stats_age is col 6 and must not be diffed. + return PgStatWALPG18, 7, [2]int{2, 5} } - return PgStatWALDefault, 11 + // cols 2-9: wal,KiB..buffers_full; col 10 (stats_age) excluded. + return PgStatWALDefault, 11, [2]int{2, 9} } diff --git a/internal/query/wal_test.go b/internal/query/wal_test.go index ce3ecd9..9a33ac7 100644 --- a/internal/query/wal_test.go +++ b/internal/query/wal_test.go @@ -7,13 +7,35 @@ import ( "testing" ) -// Test_StatWALQueries tests query, executing it against all supported Postgres versions. +func Test_SelectStatWALQuery(t *testing.T) { + testcases := []struct { + version int + wantNcols int + wantDiffIntvl [2]int + }{ + {version: 140000, wantNcols: 11, wantDiffIntvl: [2]int{2, 9}}, + {version: 150000, wantNcols: 11, wantDiffIntvl: [2]int{2, 9}}, + {version: 170000, wantNcols: 11, wantDiffIntvl: [2]int{2, 9}}, + // PG 18: removed wal_write/wal_sync; stats_age must be outside the diff interval. + {version: 180000, wantNcols: 7, wantDiffIntvl: [2]int{2, 5}}, + } + + for _, tc := range testcases { + t.Run(fmt.Sprintf("version/%d", tc.version), func(t *testing.T) { + _, gotNcols, gotDiffIntvl := SelectStatWALQuery(tc.version) + assert.Equal(t, tc.wantNcols, gotNcols) + assert.Equal(t, tc.wantDiffIntvl, gotDiffIntvl) + }) + } +} + +// Test_StatWALQueries tests query execution against all supported Postgres versions. func Test_StatWALQueries(t *testing.T) { versions := []int{140000, 150000, 160000, 170000, 180000} for _, version := range versions { t.Run(fmt.Sprintf("pg_stat_wal/%d", version), func(t *testing.T) { - tmpl, _ := SelectStatWALQuery(version) + tmpl, _, _ := SelectStatWALQuery(version) opts := NewOptions(version, "f", "off", 256, "public") q, err := Format(tmpl, opts) diff --git a/internal/stat/postgres_test.go b/internal/stat/postgres_test.go index 0cabde0..91e64fc 100644 --- a/internal/stat/postgres_test.go +++ b/internal/stat/postgres_test.go @@ -335,6 +335,53 @@ func Test_diff(t *testing.T) { assert.Error(t, err) } +// Test_diff_pg18_wal_stats_age reproduces issue #132: when showing WAL statistics on PG 18, +// the stats_age column ('19 days 02:52:00') was incorrectly included in the diff interval +// and caused a parse error. The correct DiffIntvl for PG 18 WAL view is [2, 5]. +func Test_diff_pg18_wal_stats_age(t *testing.T) { + // PG 18 pg_stat_wal columns: source, waldir_size, wal_KiB, records, fpi, buffers_full, stats_age + prev := PGresult{ + Valid: true, Ncols: 7, Nrows: 1, + Cols: []string{"source", "waldir_size", "wal,KiB", "records", "fpi", "buffers_full", "stats_age"}, + Values: [][]sql.NullString{ + { + {String: "WAL", Valid: true}, + {String: "64 MB", Valid: true}, + {String: "12000.00", Valid: true}, + {String: "1000", Valid: true}, + {String: "50", Valid: true}, + {String: "10", Valid: true}, + {String: "19 days 02:52:00", Valid: true}, + }, + }, + } + curr := PGresult{ + Valid: true, Ncols: 7, Nrows: 1, + Cols: []string{"source", "waldir_size", "wal,KiB", "records", "fpi", "buffers_full", "stats_age"}, + Values: [][]sql.NullString{ + { + {String: "WAL", Valid: true}, + {String: "64 MB", Valid: true}, + {String: "12100.00", Valid: true}, + {String: "1010", Valid: true}, + {String: "55", Valid: true}, + {String: "11", Valid: true}, + {String: "19 days 03:01:38", Valid: true}, + }, + }, + } + + // Old (buggy) interval [2, 9]: stats_age at col 6 is inside the diff range and causes parse error. + _, err := diff(curr, prev, 1, [2]int{2, 9}, 0) + assert.Error(t, err, "stats_age should not be diffable with interval [2,9] on PG18 WAL data") + + // Correct interval [2, 5]: stats_age at col 6 is outside the diff range. + got, err := diff(curr, prev, 1, [2]int{2, 5}, 0) + assert.NoError(t, err) + assert.Equal(t, "19 days 03:01:38", got.Values[0][6].String, "stats_age should be copied as-is") + assert.Equal(t, "100.00", got.Values[0][2].String, "wal,KiB delta should be computed") +} + func Test_sort(t *testing.T) { res := newTestPGresult() testcases := []struct { diff --git a/internal/view/view.go b/internal/view/view.go index f39bc3d..4d8a13d 100644 --- a/internal/view/view.go +++ b/internal/view/view.go @@ -304,7 +304,7 @@ func (v Views) Configure(opts query.Options) error { view.QueryTmpl = query.SelectStatStatementsTimingQuery(opts.Version) v[k] = view case "wal": - view.QueryTmpl, view.Ncols = query.SelectStatWALQuery(opts.Version) + view.QueryTmpl, view.Ncols, view.DiffIntvl = query.SelectStatWALQuery(opts.Version) v[k] = view } }