From a4fefb3e0dc5451166ebef6adcc082a57ddba039 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Sat, 11 Apr 2026 17:03:06 +0900 Subject: [PATCH] Honor passed-in database OIDs in pgstat_database.c Three routines in pgstat_database.c incorrectly ignore the database OID provided by their caller, using MyDatabaseId instead: - pgstat_report_connect() - pgstat_report_disconnect() - pgstat_reset_database_timestamp() The first two functions, for connection and disconnection, each have a single caller that already passes MyDatabaseId. This was harmless, still incorrect. The timestamp reset function also has a single caller, but in this case the issue has a real impact: it fails to reset the timestamp for the shared-database entry (datid=0) when operating on shared objects. This situation can occur, for example, when resetting counters for shared relations via pg_stat_reset_single_table_counters(). There is currently one test in the tree that checks the reset of a shared relation, for pg_shdescription, we rely on it to check what is stored in pg_stat_database. As stats_reset may be NULL, two resets are done to provide a baseline for comparison. Author: Chao Li Reviewed-by: Michael Paquier Reviewed-by: Dapeng Wang Discussion: https://postgr.es/m/ABBD5026-506F-4006-A569-28F72C188693@gmail.com Backpatch-through: 15 --- src/backend/utils/activity/pgstat_database.c | 6 +++--- src/test/regress/expected/stats.out | 18 ++++++++++++++++++ src/test/regress/sql/stats.sql | 8 ++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c index 10ee3e57a89..3398a798460 100644 --- a/src/backend/utils/activity/pgstat_database.c +++ b/src/backend/utils/activity/pgstat_database.c @@ -197,7 +197,7 @@ pgstat_report_connect(Oid dboid) pgLastSessionReportTime = MyStartTimestamp; - dbentry = pgstat_prep_database_pending(MyDatabaseId); + dbentry = pgstat_prep_database_pending(dboid); dbentry->sessions++; } @@ -212,7 +212,7 @@ pgstat_report_disconnect(Oid dboid) if (!pgstat_should_report_connstat()) return; - dbentry = pgstat_prep_database_pending(MyDatabaseId); + dbentry = pgstat_prep_database_pending(dboid); switch (pgStatSessionEndCause) { @@ -356,7 +356,7 @@ pgstat_reset_database_timestamp(Oid dboid, TimestampTz ts) PgStat_EntryRef *dbref; PgStatShared_Database *dbentry; - dbref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, MyDatabaseId, InvalidOid, + dbref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, dboid, InvalidOid, false); dbentry = (PgStatShared_Database *) dbref->shared_stats; diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 43b7873c62b..048ebbb552a 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -791,6 +791,8 @@ SELECT (n_tup_ins + n_tup_upd) > 0 AS has_data FROM pg_stat_all_tables t (1 row) +-- stats_reset may not be set for datid=0 and shared objects in +-- pg_stat_database, so reset once. SELECT pg_stat_reset_single_table_counters('pg_shdescription'::regclass); pg_stat_reset_single_table_counters ------------------------------------- @@ -804,6 +806,22 @@ SELECT (n_tup_ins + n_tup_upd) > 0 AS has_data FROM pg_stat_all_tables f (1 row) +SELECT stats_reset AS shared_db_reset_before + FROM pg_stat_database WHERE datid = 0 \gset +-- Second reset for comparison. +SELECT pg_stat_reset_single_table_counters('pg_shdescription'::regclass); + pg_stat_reset_single_table_counters +------------------------------------- + +(1 row) + +SELECT stats_reset > :'shared_db_reset_before'::timestamptz AS has_updated + FROM pg_stat_database WHERE datid = 0; + has_updated +------------- + t +(1 row) + -- set back comment \if :{?description_before} COMMENT ON DATABASE :"datname" IS :'description_before'; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index ed0bc1ef074..3b4c4908cf7 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -396,9 +396,17 @@ COMMIT; -- check that the stats are reset. SELECT (n_tup_ins + n_tup_upd) > 0 AS has_data FROM pg_stat_all_tables WHERE relid = 'pg_shdescription'::regclass; +-- stats_reset may not be set for datid=0 and shared objects in +-- pg_stat_database, so reset once. SELECT pg_stat_reset_single_table_counters('pg_shdescription'::regclass); SELECT (n_tup_ins + n_tup_upd) > 0 AS has_data FROM pg_stat_all_tables WHERE relid = 'pg_shdescription'::regclass; +SELECT stats_reset AS shared_db_reset_before + FROM pg_stat_database WHERE datid = 0 \gset +-- Second reset for comparison. +SELECT pg_stat_reset_single_table_counters('pg_shdescription'::regclass); +SELECT stats_reset > :'shared_db_reset_before'::timestamptz AS has_updated + FROM pg_stat_database WHERE datid = 0; -- set back comment \if :{?description_before}