From 7801e223034498a51ca7f60a61de5a9c181b1797 Mon Sep 17 00:00:00 2001
From: Shinya Kato <shinya11.kato@gmail.com>
Date: Thu, 11 Jun 2026 16:04:30 +0900
Subject: [PATCH v1 2/5] Count md5_password_set in pg_stat_deprecated_features

---
 doc/src/sgml/monitoring.sgml                  |  9 ++++++
 src/backend/libpq/crypt.c                     | 19 +++++++-----
 .../activity/pgstat_deprecated_features.c     |  2 +-
 src/include/catalog/pg_proc.dat               |  2 +-
 src/include/pgstat.h                          |  5 ++--
 src/test/regress/expected/stats.out           | 30 +++++++++++++++++--
 src/test/regress/sql/stats.sql                | 15 ++++++++++
 7 files changed, 68 insertions(+), 14 deletions(-)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 25f993496d8..5195d160dc4 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -3501,6 +3501,15 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    The tracked deprecated features are:
 
    <variablelist>
+    <varlistentry>
+     <term><literal>md5_password_set</literal></term>
+     <listitem>
+      <para>
+       Setting an MD5-encrypted password, e.g., with <command>CREATE
+       ROLE</command> or <command>ALTER ROLE</command>.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
   </para>
  </sect2>
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 28857773d9c..345a9d43698 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -21,6 +21,7 @@
 #include "libpq/crypt.h"
 #include "libpq/scram.h"
 #include "miscadmin.h"
+#include "pgstat.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/syscache.h"
@@ -241,13 +242,17 @@ encrypt_password(PasswordType target_type, const char *role,
 						   MAX_ENCRYPTED_PASSWORD_LEN)));
 	}
 
-	if (md5_password_warnings &&
-		get_password_type(encrypted_password) == PASSWORD_TYPE_MD5)
-		ereport(WARNING,
-				(errcode(ERRCODE_WARNING_DEPRECATED_FEATURE),
-				 errmsg("setting an MD5-encrypted password"),
-				 errdetail("MD5 password support is deprecated and will be removed in a future release of PostgreSQL."),
-				 errhint("Refer to the PostgreSQL documentation for details about migrating to another password type.")));
+	if (get_password_type(encrypted_password) == PASSWORD_TYPE_MD5)
+	{
+		pgstat_count_deprecated_feature(PGSTAT_DEPRECATED_FEATURE_MD5_PASSWORD_SET);
+
+		if (md5_password_warnings)
+			ereport(WARNING,
+					(errcode(ERRCODE_WARNING_DEPRECATED_FEATURE),
+					 errmsg("setting an MD5-encrypted password"),
+					 errdetail("MD5 password support is deprecated and will be removed in a future release of PostgreSQL."),
+					 errhint("Refer to the PostgreSQL documentation for details about migrating to another password type.")));
+	}
 
 	return encrypted_password;
 }
diff --git a/src/backend/utils/activity/pgstat_deprecated_features.c b/src/backend/utils/activity/pgstat_deprecated_features.c
index 47e34b78243..489fce29069 100644
--- a/src/backend/utils/activity/pgstat_deprecated_features.c
+++ b/src/backend/utils/activity/pgstat_deprecated_features.c
@@ -27,7 +27,7 @@
  * be kept in the same order, sorted by name.
  */
 static const char *const pgstat_deprecated_feature_names[] = {
-	/* entries are added by later commits */
+	[PGSTAT_DEPRECATED_FEATURE_MD5_PASSWORD_SET] = "md5_password_set",
 };
 
 StaticAssertDecl(lengthof(pgstat_deprecated_feature_names) == PGSTAT_NUM_DEPRECATED_FEATURES,
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2823f856424..37de65c65f6 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6070,7 +6070,7 @@
   prosrc => 'pg_stat_get_lock' },
 
 { oid => '8987', descr => 'statistics: deprecated feature usage',
-  proname => 'pg_stat_get_deprecated_features', prorows => '0',
+  proname => 'pg_stat_get_deprecated_features', prorows => '1',
   proretset => 't', provolatile => 'v', proparallel => 'r',
   prorettype => 'record', proargtypes => '',
   proallargtypes => '{text,int8,timestamptz,timestamptz}',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index a43b56f247d..241e7757492 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -276,10 +276,11 @@ typedef struct PgStat_CheckpointerStats
 /* Deprecated features counted by pg_stat_deprecated_features */
 typedef enum PgStat_DeprecatedFeature
 {
-	/* entries are added by later commits */
+	PGSTAT_DEPRECATED_FEATURE_MD5_PASSWORD_SET,
 } PgStat_DeprecatedFeature;
 
-#define PGSTAT_NUM_DEPRECATED_FEATURES 0
+#define PGSTAT_NUM_DEPRECATED_FEATURES \
+	(PGSTAT_DEPRECATED_FEATURE_MD5_PASSWORD_SET + 1)
 
 typedef struct PgStat_DeprecatedFeatureStats
 {
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index 1444b2eec9a..9f93a450a53 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -1077,6 +1077,29 @@ SELECT stats_reset > :'slru_notify_reset_ts'::timestamptz FROM pg_stat_slru WHER
  t
 (1 row)
 
+-- Test deprecated feature usage statistics
+-- Setting an MD5-encrypted password must be counted even when
+-- md5_password_warnings is disabled.
+SET md5_password_warnings = off;
+SELECT usage_count AS deprecated_md5_set_count
+  FROM pg_stat_deprecated_features WHERE name = 'md5_password_set' \gset
+CREATE ROLE regress_stats_md5 PASSWORD 'md5912f4d9b58e4731e3e9f6dd9eebbbca5';
+DROP ROLE regress_stats_md5;
+RESET md5_password_warnings;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT usage_count = :deprecated_md5_set_count + 1 AS incremented,
+       last_used IS NOT NULL AS has_last_used
+  FROM pg_stat_deprecated_features WHERE name = 'md5_password_set';
+ incremented | has_last_used 
+-------------+---------------
+ t           | t
+(1 row)
+
 -- Test that reset_shared with archiver specified as the stats type works
 SELECT stats_reset AS archiver_reset_ts FROM pg_stat_archiver \gset
 SELECT pg_stat_reset_shared('archiver');
@@ -1121,9 +1144,10 @@ SELECT stats_reset > :'checkpointer_reset_ts'::timestamptz FROM pg_stat_checkpoi
 
 -- The deprecated features view should list the expected features
 SELECT name FROM pg_stat_deprecated_features ORDER BY name COLLATE "C";
- name 
-------
-(0 rows)
+       name       
+------------------
+ md5_password_set
+(1 row)
 
 -- Test that reset_shared with deprecated_features specified as the stats type works
 SELECT max(stats_reset) AS deprecated_features_reset_ts FROM pg_stat_deprecated_features \gset
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index f0711c41a87..bf88b08c4cf 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -498,6 +498,21 @@ SELECT pg_stat_reset_slru();
 SELECT stats_reset > :'slru_commit_ts_reset_ts'::timestamptz FROM pg_stat_slru WHERE name = 'commit_timestamp';
 SELECT stats_reset > :'slru_notify_reset_ts'::timestamptz FROM pg_stat_slru WHERE name = 'notify';
 
+-- Test deprecated feature usage statistics
+
+-- Setting an MD5-encrypted password must be counted even when
+-- md5_password_warnings is disabled.
+SET md5_password_warnings = off;
+SELECT usage_count AS deprecated_md5_set_count
+  FROM pg_stat_deprecated_features WHERE name = 'md5_password_set' \gset
+CREATE ROLE regress_stats_md5 PASSWORD 'md5912f4d9b58e4731e3e9f6dd9eebbbca5';
+DROP ROLE regress_stats_md5;
+RESET md5_password_warnings;
+SELECT pg_stat_force_next_flush();
+SELECT usage_count = :deprecated_md5_set_count + 1 AS incremented,
+       last_used IS NOT NULL AS has_last_used
+  FROM pg_stat_deprecated_features WHERE name = 'md5_password_set';
+
 -- Test that reset_shared with archiver specified as the stats type works
 SELECT stats_reset AS archiver_reset_ts FROM pg_stat_archiver \gset
 SELECT pg_stat_reset_shared('archiver');
-- 
2.47.3

